리액트

컴포넌트

jeongsso 2023. 2. 18. 19:32

리액트를 사용하여 애플리케이션의 인터페이스를 설계할 때

사용자가 볼 수 있는 요소는 여러 가지 컴포넌트로 구성되어 있습니다.

 

일단 클래스형 컴포넌트에 대해 살펴본뒤, 컴포넌트를 새로 만들고 사용하는 방법을 알아보겠습니다.

그리고 컴포넌트의 속성 값을 지닌 props와 상태 값을 지닌 state를 사용하는 방법도 알아보겠습니다.

 

 

 

1.   클래스형 컴포넌트

App 컴포넌트는 함수 컴포넌트며, 코드가 다음과 같다.

// App.js
import './App.css';

function App() {
  const name = '리액트';
  return <div className = "react">{name}</div>;
}

export default App;

컴포넌트를 선언하는 방식은 두 가지입니다.

하나는 함수 컴포넌트고,

또 하나는 클래스형 컴포넌트입니다.

 

클래스형 컴포넌트는 아래와 같습니다.

// App.js
import {Component} from 'react';

class App extends Component {
  render() {
    const name = 'react';
    return <div className = "react">{name}</div>;
  }
}

export default App;

클래스형 컴포넌트로 바뀌었지만 역할은 이전에 보았던 함수 컴포넌트와 동일하다.

 

클래스형과 함수형의 차이는

클래스형인 경우 이후 배울 state 기능 및 라이프 사이클 기능을 사용할 수 있다는 것과

임의 메서드를 정의할 수 있다는 것이다.

클래스형 컴포넌트에서는 render 함수가 꼭 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 한다.

 

함수 형의 장점은

클래스형 보다 선언하기 훨씬 편하고, 메모리 자원도 클래스형 컴포넌트보다 덜 사용한다.

또한, 프로젝트를 완성하여 빌드한 후 배포할 때도 함수 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작다.

단점은

state와 라이프사이클 API 사용이 불가능했었으나

리액트 v16.8 업데이트 이후 Hooks라는 기능으로 해결되었다.

완전히 클래스형과 같진 않지만 비슷하다!

 

 

일단 리액트의 기본기를 익히고,

후반부에서는 Hooks를 완벽히 이해해보자.

🤔  ES6의 클래스 문법
ES6 이전에는 자바스크립트에 클래스(class)가 없었습니다.
뭐 개념 자체는 있었지만, 그것을 구현하려면 class 대신 prototype이라는 문법을 사용해서 작업했어야 했습니다.
function Dog(name) {
  this.name = name;
}

Dog.prototype.say = function () {
  console.log(this.name + '멍멍');
}

var dog = new Dog('겸둥이');
dog.say(); // 겸둥이 : 멍멍

 

ES6 문법부터는 이것과 기능이 똑같은 코드를 class를 사용하여 다음과 같이 작성할 수 있다.
class Dog {
  constructor(name) {
    this.name = name;
  }
  say() {
    console.log(this.name + '멍멍');
  }
}

const dog = new Dog('흰둥이');
dog.say(); // 흰둥이 : 멍멍​

 

 

 

 


2.   첫 컴포넌트 생성

첫 번째 컴포넌트를 만들어 봅시다.

 

 

(1)  src 디렉터리에 MyComponent.js 파일 생성

컴포넌트를 만들려면 컴포넌트 코드를 선언해야 합니다.

컴포넌트 코드를 선언할 파일을 만들고 새 파일 이름을 입력해 주면 됩니다.

이름은 뭐 JeongSeo.js로 !

 

(2)  코드 작성하기

JeongSeo.js 파일을 열고 새 컴포넌트 코드를 작성해봅시다.

먼저 함수 컴포넌트로하고, 나중에 클래스형으로 해보겠습니다,

const JeongSeo = () => {
  return <div> 나의 멋진 컴포넌트! </div>;
};

export default JeongSeo;

이번에 작성한 코드는 이전에 봤던 App 컴포넌트랑 조큼 다른데,

함수작성 때 function 대신 화살표 함수를 사용해서 만들었습니다.

 

간결하고 가독성이 좋아 화살표 함수를 사용하겠습니다!

 

 

 

(3)  모듈 내보내기 및 불러오기

모듈 내보내기 (export)

위에 작성된 코드 맨 아래에 보면

'export default JeongSeo;' 라고 적혀있다.

 

이 코드는 다른 파일에서 이 파일을 import 할 때, 위에서 선언한 JeongSeo 클래스를 불러오도록 설정한다.

 


 

모듈 불러오기 (import)

이번에는 App 컴포넌트에서 JeongSeo 컴포넌트를 불러와 사용해보자.

기존에 있던 App 컴포넌트 코드를 다 지우고 다시 작성해보자.

import JeongSeo from './JeongSeo';

const App = () => {
  retunr <JeongSeo/>;
};

export default App;

 

위 코드에서 import 구문을 사용하는 두 번째 줄은 우리가 만든 JeongSeo 컴포넌트를 불러온다.

 

 

 

 


3.   props

props는 properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소다.

props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 사용할 수 있다.

 

 

(1)  JSX 내부에서 props 렌더링

우선 JeangSeo 컴포넌트를 수정하여 해당 컴포넌트에서 name이라는 props를 렌더링하도록 설정해 보자!

props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용할 수 있습니다.

props 를 렌더링할 때 앞에서 배웠던 것처럼 JSX 내부에서 { } 기호로 감싸주면 됩니다!

const JeongSeo = props => {
  return <div> 안녕하세요, 제이름은 {props.name}입니다. </div>;
};

export default JeongSeo;

 

 

 

(2) 컴포넌트를 사용할 때 props 값 지정하기

App 컴포넌트에서 JeongSeo의 props 값을 지정해 보겠습니다.

App.js의 코드를 다음과 같이 수정해 보자.

// App.js
import JeongSeo from './JeongSeo';

const App = () => {
  return <JeongSeo name = "React" />;
};

export default App;

 

 

 

 

(3) props 기본값 성정 : defaultProps

방금 위에서 설정한 name 값을 지우고 다시 저장해보자.

// App.js
import JeongSeo from './JeongSeo';

const App = () => {
  return <JeongSeo/>;
};

export default App;

현재 name 값을 지정하지 않았기 때문에 브라우저에는 '안녕하세요. 제 이름은 입니다.'가 나타날 것입니다.

지금처럼 props를 따로 지정하지 않았을 때 보여줄 기본값을 설정하는 defaultProps에 대해 알아보자.

 

// JeongSeo.js
const JeongSeo = props => {
  return <div> 안녕하세요, 제 이름은 {props.name}입니다. </div>
};

JeongSeo.defaultProps = {
  name : '기본 이름'
};

export default JeongSeo;

하면 props를 지정하지 않아도 

브라우저에 '안녕하세요, 제 이름은 기본 이름입니다.' 라고 뜰겁니다!

 

 

 

 

(4) 태그 사이의 내용을 보여주는 children

리액트 컴포넌트를 사용할 때 컴포넌트 태그 사이의 내용을 보여주는 props가 있는데,

이를 children이라고 한다.

// App.js
import JeongSeo from './JeongSeo';

const App = () => {
  return <JeongSeo> 리액트 </JeongSeo>;
};

export default App;

위 코드에서 JeongSeo 태그 사이에 작성한 리액트라는 문자열을 JeongSeo 내부에서 보여주려면

props.children 값을 보여줘야 한다.

 

JeongSeo를 수정해보자.

// JeongSeo.js
const JeongSeo = props => {
  return (
    <div> 
      안녕하세요, 제 이름은 {props.name}입니다.<br/>
      children 값은 {props.children}
      입니다.
    </div>
  );
};

JeongSeo.defaultProps = {
  name : '기본 이름'
};

export default JeongSeo;

 

 

 

 

 

 

(5) 태그 사이의 내용을 보여주는 children

현재 JeongSeo에서 props 값을 조회할 때마다 props.name, props.children과 같이

props.이라는 키워드를 앞에 붙여주고 있습니다.

이러한 작업을 더 편하게 하기 위해 ES6의 비구조화 할당 문법을 사용하여 내부 값을 바로 추출하는 방법을 알아보자!

JeongSeo를 다음과 같이 수정해보시오!

// JeongSeo.js
const JeongSeo = props => {
  const {name, childremn} = props;
  return (
    <div> 
      안녕하세요, 제 이름은 {name}입니다.<br/>
      children 값은 {children}
      입니다.
    </div>
  );
};

JeongSeo.defaultProps = {
  name : '기본 이름'
};

export default JeongSeo;

이렇게 코드를 작성하면 name 값과 children 값을 더 짧은 코드로 사용할 수 있습니다.

방금 사용한, 객체에서 값을 추출하는 문법비구조화 할당이라고 부른다.

이 문법은 구조 분해 문법이라고도 불리며,

함수의 파라미터 부분에서도 사용할 수 있습니다.

 

만약 함수의 파라미터가 객체라면 그값을 바로 비구조화해서 사용하는 것입니다.

// JeongSeo.js
const JeongSeo = ({name, children}) => {
  return (
    <div> 
      안녕하세요, 제 이름은 {name}입니다.<br/>
      children 값은 {children}
      입니다.
    </div>
  );
};

JeongSeo.defaultProps = {
  name : '기본 이름'
};

export default JeongSeo;

캬.. 짧아졌다.

const {name, children} = props; 가

({name, children})으로 짧게 매개변수로 넣어 쉬워졌습니다.

 

 

 

 

(6) propTypes를 통한 props 검증

컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때propTypes를 사용합니다.

컴포넌트의 propTypes를 지정하는 방법은 defaultProp을 설정하는 것과 비슷합니다.

우선 propTypes를 사용하려면 코드 상단에 import 구문을 사용하여 불러와야 합니다.

// JeongSeo.js
import PropTypes from 'prop-types';

const JeongSeo = ({name, children}) => {
 ( ... )

PropTypes를 불러왔다면 다음과 같이 입력해 보세요.

// JeongSeo.js
import PropTypes from 'prop-types';

const JeongSeo = ({name, children}) => {
  return (...);
};

JeongSeo.defaultProps = {
  name = '기본 이름'
};

JeongSeo.propTypes = {
  name : PropTypes.string
};

export default JeongSeo;

이렇게 설정해 주면 name 값은 무조건 문자열 형태로 전달해야 된다는 것을 의미합니다.

App 컴포넌트에서 name 값을 문자열이 아닌 숫자로 전달한 뒤 개발자 도구의 Console 탭을 열어보세요.

// App.js
import JeongSeo from './JeongSeo';

const App = () => {
  return <JeongSeo name = {3}> 리액트 </JeongSeo>
};

export default App;

아마 값이 나타나긴 하겠지만, 콘솔에 경고 메세지를 출력하여 개발자에게 propTypes가 잘못되었다는 것을 알려줍니다.

오류 메세지를 확인했다면 name값을 다시 설정해 보자.

 

오류는 아마

Invalid prop 'name' of type 'number' supplied to 'MyComponent', expected 'string'.

이걸거에요 ㅎㅎ

// App.js
import JeongSeo from './JeongSeo';

const App = () => {
  return <JeongSeo name = "React"> 리액트 </JeongSeo>;
};

export default App;

자, 오류가 사라졌죠?

 

 

 

(6 - 1)  isRequired를 사용하여 필수 propTypes 설정

PropTypes를 지정하지 않았을 때 경고 메시지를 띄워 주는 작업을 해보자.

propTypes를 지정할 때 뒤에 isRequired를 붙여주면 됩니다.

이번에는 favoriteNumber라는 숫자를 필수 props로 지정해보자.

// JeongSeo.js
import PropTypes form 'prop-types';

const JeongSeo = ({ name, favoriteNumber, children }) => {
  return (
    <div>
      안녕하세요, 제 이름은 {name}입니다. <br/>
      children 값은 {children}
      입니다.
      <br/>
      제가 좋아하는 숫자는 {favoriteNumber}입니다.
    </div>
  );
};

JeongSeo.defaultProps = {
  name : '기본 이름'
};

JeongSeo.propTypes = {
  name : PropTypes.string,
  favoriteNumber : PropTypes.number.isRequired
};

export default JeongSeo;

아직 favoriteNumber를 설정하지 않았기 때문에 오류가 나타날 것입니다.

아마

Failed prop type : The prop 'favoriteNumber' is marked as required in 'JeongSeo',

but its value is 'undefined'.

 

경고를 확인 했다면 JeongSeo에게 favoriteNumber 값을 제대로 전달해보자.

// App.js
import JeongSeo from './Jeongseo';

const App = () => {
  return (
    <JeongSeo name = "React" favoriteNumber = {1}>
      리액트
    </JeongSeo>
  );
};

export default App;

 

 

 

 

(6 - 2)  더 많은 PropTypes 종류

여러 종류가 있는데 함 살펴보자!

array : 배열

arrayOf(다른 PropType) : 특정 PropType으로 이루어진 배열을 의미합니다.

예를 들어, arrayOf(PropTypes.number)는 숫자로 이루어진 배열입니다.

bool : true 혹은 false 값

func : 함수

number : 숫자

object : 객체

string : 문자열

symbol : ES6의 Symbol

node : 렌더링할 수 있는 모든 것(숫자, 문자열 혹은 JSX 코드, children도 node PropType 입니다.)

instanceOf(클래스) : 특정 클래스의 인스턴스

oneOf([ 'dog', 'cat' ]) : 주어진 배열 요소 중 값 하나

oneOfType([ React.PropTypes.string, PropTypes.number ]) : 주어진 배열 안의 종류 중 하나

objectOf(React.PropTypes.number) : 객체의 모든 키 값이 인자로 주어진 PropType인 객체

shape({ name : PropTypes.string, num : ProTypes.number })  : 주어진 스키마를 가진 객체

any : 아무 종류

 

 

 

 

 

(7) 클래스형 컴포넌트에서 props 사용하기

클래스형 컴포넌트에서 props를 사용할 때는 render 함수에서 this.props를 조회하면 된다.

그리고 defaultProps와 propTypes는 똑같은 방식으로 설정할 수 있다.

JeongSeo를 다음과 같이 클래스형으로 변환해보자.

// JeongSeo.js
import { Component } form 'react';
import PropTypes from 'prop-types';

class JeongSeo extend Component {
  render() {
    const { name, favoriteNumber, children } = this.props; // 비구조화 할당
    return (
      <div>
        안녕하세요, 제 이름은 {name}입니다. <br/>
        children 값은 {children}
        입니다.
        <br/>
        제가 좋아하는 숫자는 {favoriteNumber}입니다.
      </div>
    );
  }
}

JeongSeo.defaultProps = {
  name : '기본 이름'
};

JeongSeo.propTypes = {
  name : PropTypes.string,
  favoriteNumber : PropTypes.number.isRequired
};

export default JeongSeo;

클래스형 컴포넌트에서 defaultProps를 설정할 때 class 내부에서 지정하는 방법도 있다.

// JeongSeo.js
import { Component } form 'react';
import PropTypes from 'prop-types';

class JeongSeo extend Component {
  static defaultProps = {
    name : '기본 이름'
  };
  static propTypes = {
    name : PropTypes.string,
    favoriteNumber : PropTypes.number.isRequired
  };
  render() {
    const { name, favoriteNumber, children } = this.props; // 비구조화 할당
    return ( ... );
  }
}

export default JeongSeo;

defaultProps와 propTypes는 필수 사항은 아니니 꼭 사용하진 않아도 됩니다.

 

 

 

 


4.   state

리액트에서 state컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.

props컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며,

컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다.

 

props를 바꾸려면 부모 컴포넌트에서 바꾸어 주어야 합니다.

 

 

예를 들어, 현재 상황에서는 App 컴포넌트에서 JeongSeo를 사용할 때 props를 바꾸어 주어야 값이 변경될 수 있다.

반면 JeongSeo에서는 전달받은 name 값을 직접 바꿀 수 없다.

 

리액트에는 두 가지 종류의 state가 있다.

하나는 클래스형 컴포넌트가 지니고 있는 state고,

다른 하나는 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state입니다.

 

 

 

 

(1) 클래스형 컴포넌트의 state

자 새로운 컴포넌트를 만들어봅시다.

Couter.js 파일을 scr 디렉터리에 생성하여 다음과 같은 코드를 작성해 봅시다.

// Counter.js
import { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    // state의 초깃값 설정하기
    this.state = {
      number : 0
    };
  }
  render() {
    const { number } = this.state; // state를 조회할 때는 this.state로 조회한다.
    return (
      <div>
        <h1>{ number }</h1>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
          onClick = {() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number : number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

위 파일에서 각 코드가 어떤 역할을 했는지 알아보자.

 

컴포넌트에서 state를 설정할 때는 다음과 같이 constructor 메서드를 작성하여 설정한다.

constructor(props) {
  super(props);
  // state의 초깃값 설정하기
  this.state = {
    number : 0
  };
}

이는 컴포넌트의 생성자 메서드입니다.

클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해야한다.

이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는

리액트의 Component 클래스가 지닌 생성자 함수를 호출해 준다.

 

그 다음에는 this.state 값에 초깃값을 설정해주었다.

컴포넌트의 state는 객체 형식이어야 한다.

 

이제 render 함수를 확인해 보자.

render() {
  const {number} = this.state; // state를 조회할 때는 this.state로 조회합니다.
  return (
    <div>
      <h1>{number}</h1>
      <button
        // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
        onClick = {() => {
          // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.
          this.setState({ number : number + 1 });
        }}
      >
        + 1
      </button>
    </div>
  );
}

render 함수에서 현재 state를 조회할 때는 this.state를 조회하면 됩니다.

그리고 button 안에 onClick이라는 값을 props로 넣어 주었는데,

이는 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 한다.

이를 이벤트 설정이라고 하는데, 리액트의 이벤트 시스템은 추후 더 알아볼 것이다.

 

 

이벤트로 설정할 함수를 넣어 줄 때는 화살표 함수 문법을 사용하여 넣어야한다.

함수 내부에서는 this.setState라는 함수를 사용했는데,

이 함수가 state 값을 바꿀 수 있게 한다.

 

코드를 작성했으면 Counter 컴포넌트를 App에서 불러와 렌더링해보자.

기존 JeongSeo는 이제 필요없슴니다!

// App.js
import Counter from './Counter';

const App = () => {
  return <Counter/>
};

export default App;

 

 

(1 - 1) state 객체 안에 여러 값이 있을 때

state 객체 안에는 여러 값이 있을 수 있다.

Counter 컴포넌트를 다음과 같이 수정해보자.

// Counter.js
import { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    // state의 초깃값 설정하기
    this.state = {
      number : 0,
      fixedNumber : 0
    };
  }
  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회한다.
    return (
      <div>
        <h1>{ number }</h1>
        <h2>바뀌지 않는 값 : { fixedNumber }</h2>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
          onClick = {() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number : number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

현재 state 안에 fixedNumber라는 또 다른 값을 추가해 주었습니다.

버튼이 클릭될 때 fixedNumber 값은 그대로 두고 number 값만 바꿀것 인데,

그렇다고 해서 this.setState 함수를 사용할 때 인자로 전달되는 객체 내부에 fixedNumber를 넣지는 않았다.

this.setState 함수는 인자로 전달된 객체 안에 들어 있는 값만 바꾸어 준다.

 

 

 

(1 - 2) state를 constructor에서 꺼내기

앞에서 state의 초깃값을 지정하기 위해 constructor 메서드를 선언해 줬는데,

또 다른 방식으로도 state의 초깃값을 지정해 줄 수 있다.

// Counter.js
import { Component } from 'react';

class Counter extends Component {
  state = {
    number : 0,
    fixedNumber : 0
  };
  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회한다.
    return (
      <div>
        <h1>{ number }</h1>
        <h2>바뀌지 않는 값 : { fixedNumber }</h2>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
          onClick = {() => {
            // this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
            this.setState({ number : number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

이렇게 하면 constructor 메서드를 선언하지 않고도 state 초깃값을 설정할 수 있습니다.

이 책에서는 앞으로도 state를 사용할 때 이 방식을 사용하여 state의 초깃값을 설정하겠다.

 

 

 

 

(1 - 3) this.setState에 객체 대신 함수 인자 전달하기

this.setState를 사용해 state 값을 업데이트할 때상태가 비동기적으로 업데이트된다.

만약 다음과 같이 onClick에 설정한 함수 내부에서 this.setState를 두 번 호출하면?!

// Counter.js - button onClick
onClick = {() => {
  // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.
  this.setState({ number : number + 1 });
  this.setState({ number : this.state.number + 1 });
}}

코드를 위와 같이 작성하면 this.setState를 두 번 사용하는 것임에도 불구하고 버튼을 클릭할 때 숫자가 1씩 더해진다.

this.setState를 사용한다 해서 state 값이 바로 바뀌지는 않기 때문입니다.

 

이에 대한 해결책은 this.setState 를 사용할 때 객체 대신에 함수를 인자로 넣어주는 것이다.

this.setState의 인자로 함수를 넣어줄 땐 다음과 같이 작성한다.

<button
   // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
   onClick = {() => {
     this.setState(prevState => {
       return {
         number : prevState.number + 1
       };
     });
     // 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
     // 아래 코드는 함수에서 바로 객체를 반환한다는 의미입니다.
     this.setStat(prevState => ({
       number : prevState.number + 1
     }));
   }}
>
+ 1
</button>

화살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 {  }를 생략하면 됩니다.

 

 

예를 들어, 파라미터 a와 b를 받아 와서 합을 구해 주는 함수를 작성하고 싶다면 다음과 같이 작성하면 된다.

const sum = (a, b) => a + b;

onClick에서 두 번째로 this.setState 함수를 사용할 때화살표 함수에서 바로 객체를 반환하도록 했기 때문에

prevState => ({ })와 같은 형태로 코드가 이루어진다.

 

 

 

 

(1 - 4) this.setState가 끝난 후 특정 작업 실행하기

setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때

setState의 두 번째 함수 파라미터로 콜백 함수를 등록하여 작업을 처리할 수 있다.

<button
   // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
   onClick = {() => {
     this.setState(
       {
         number : number + 1
       },
       () => {
         console.log('방금 setState가 호출되었습니다.');
         console.log(this.state);
      }
    );
  }}
>
+ 1
</button>

 

 

 

 

(2) 함수 컴포넌트에서 useState 사용하기

리액트 16.8 이전 버전에서는 함수 컴포넌트에서 state를 사용할 수 없었습니다.

하지만 16.8 이후부터는 useState라는 함수를 사용하여 함수 컴포넌트에서도 state를 사용할 수 있게 되었습니다.

사용법은 조금 다릅니다.

 

이 과정에서 Hooks라는 것을 사용하게 되는데, 종류가 다양하지만,

useState만 배우고 나머지는 나중에 배우겠습니다!

 

 

 

 

(2 - 1) 배열 비구조화 할당

Hooks를 사용하기 전에 배열 비구조화 할당이라는 것을 알아보자.

배열 비구조화 할당은 이전에 배운 객체 비구조화 할당과 비슷하다.

즉, 배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 해주는 문법이다.

쉽게 말하면,

배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게하는 것이다.

const array = [ 1, 2 ];
const one = array[0];
const two = array[1];

array 안에 있는 값을 one과 two에 담아 주는 코드인데,

위 코드는 배열 비구조화 할당을 사용하면 다음과 같이 표현할 수 있다.

const array = [1, 2];
const [one, two ] = array;

 

 

 

 

(2 - 2) useState 사용하기

배열 비구조화 할당 문법을 알고 나면 useState 사용 방법을 쉽게 이해할 수 있다.

새 컴포넌트로 useState를 사용해보자.

// Say.js
import { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState('');
  const onClickEnter = () => setMessage('안녕하세요!');
  const onClickLeave = () => setMessage('안녕히 가세요!');
  
  return (
    <div>
      <button onClick = { onClickEnter }> 입장 </button>
      <button onClick = { onClickEnter }> 입장 </button>
      <h1>{message}</h1>
    </div>
  );
};

export default Say;

 

useState 함수의 인자에는 상태의 초깃값을 넣어 줍니다.

클래스형 컴포넌트에서의 state 초깃값은 객체 형태를 넣어 주어야 한다고 배웠는데,

useState에서는 반드시 객체가 아니여도 상관없다.

값의 형태는 자유롭다!

 

 

함수를 호출하면 배열이 반환되는데

첫 번째 원소는 현재 상태, 두 번째 원소는 상태를 바꾸어 주는 함수다.

이 함수를 세터 함수라고 부른다.

 

 

 

그리고 배열 비구조화 할당을 통해 이름을 자유롭게 정해 줄 수 있다.

현재 message와 setMessage라고 이름을 설정해 줬는데,

text와 setText라고 이름을 자유롭게 바꾸어 주어도 상관없다.

 

 

 

 

 

(2 - 3) 한 컴포넌트에서 useState 여러 번 사용하기

useState는 한 컴포넌트에서 여러 번 사용해도 상관없다.

import { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState('');
  const onClickEnter = () => setMessage('안녕하세요~!');
  const onClickLeave = () => setMessage('안녕히 가세요~!');
  
  const [color, setColor] = useState('black');
  
  return (
    <div>
      <button onClick = { onClickEnter }> 입장 </button>
      <button onClick = { onClickLeave }> 퇴장 </button>
      <h1 style = {{ color }}>{message}</h1>
      <button style = {{ color : 'red' }} onClick = {() => setColor('red')}>
        빨간색
      </button>
      <button style = {{ color : 'green' }} onClick = {() => setColor('green')}>
        초록색
      </button>
      <button style = {{ color : 'blue' }} onClick = {() => setColor('blue')}>
        파란색
      </button>     
    </div>
  );
};

export default Say;

 

 

 

 


5.   state 사용 시  주의 사항!

클래스형 컴포넌트든 함수 컴포넌트든 state를 사용할 때는 주의해야 할 사항이 있습니다.

state 값을 바꾸어야할 때setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 한다.

 

배열이나 객체를 업데이트 해야할 때배열이나 객체의 사본을 만들고 

그 사본에 값을 업데이트 한후, 그 사본의 상태를 setState 혹은 세터 함수를 통해 업데이트 한다.

 

 

객체에 대한 사본을 만들 때spread 연산자라 불리는 ...을 사용하여 처리하고,

배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용한다.

 

 

 

 

 

 

 

마무리 정리

 

props와 state는 둘 다 컴포넌트에서 사용하거나 렌더링할 데이터를 담고 있으니 비슷해보이지만,

props는 부모 컴포넌트가 설정하고,

state는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트 할 수 있다.

 

props를 사용한다해서 무조건 고정적이지는 않다.

부모 컴포넌트의 state를 자식 컴포넌트의 porps로 전달하고, 

자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하면 props도 유동적으로 사용할 수 있습니다.