Front-End/React

[React] state 개념과 useState 사용

유자맛바나나 2021. 10. 17. 03:11

 

State 

1. 정의와 개념

  • state란 지속적으로 상태를 관리할 수 있는 변수를 의미한다. 기존 JavaScript의 let, const로 선언한 변수는 화면이 Re-rendering 될 때 그 값이 초기화 되었지만, state로 선언된 변수는 React가 관리하기 때문에 화면이 Re-rendering 되어도 값이 계속 유지된다.
  • state는 컴포넌트 안에서 관리되며, 컴포넌트는 state의 값이 바뀔 때 마다 화면을 Re-rendering 한다.

2. state 사용의 차이: 함수형 컴포넌트 vs 클래스 컴포넌트

결론부터 말하자면 React 공식 문서에서 함수형 컴포넌트와 useState(React Hook)을 사용할 것을 권장한다.

개념적으로 React 컴포넌트는 항상 함수에 더 가깝습니다. Hook은 React의 정신을 희생하지 않고 함수의 사용을 권장합니다.

Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React.

출처: React Docs - Introducing Hooks

이유는 React 클래스형 컴포넌트를 사용할 때 this 키워드를 이용하게 되는데 JavaSciprt의 this는 대부분의 타 언어와 다르게 작동하며 코드의 재사용성과 구성을 어렵게 만들고 그 외에도 클래스로 인해 부가적인 문제가 발생하기 때문이다.

초기 React는 state와 lifecycle을 관리하려면 클래스형 컴포넌트에서만 가능했고 함수형 컴포넌트는 UI 표현만 가능했다. 하지만 React 16.8 버전부터 모든 컴포넌트를 함수형으로 만들 수 있게 되었고, React Hook을 이용해서 함수형 컴포넌트에서도 state와 lifecycle을 관리할 수 있게 되었다.

 

함수형 컴포넌트에서 state 사용: with useState

1. 문법

  • useState는 React Hook 중 한 가지이며, 함수형 컴포넌트에서 state를 관리하기 위해 사용한다.
  • setter함수명(클래스형 컴포넌트의 setState)은 자유롭게 작명 가능하지만, 'set변수명'으로 작성하는게 관례
  • useState(초기값)에서 초기값을 사용하지 않아도 됨
  • const 대신 let을 사용해도 무관
const [변수명, setter함수명] = useState(초기값);
예) const [fruit, setFruit] = useState('Citron');
import React, { useState } from "react";

const App = () => {
  let hello = "Welcome to Citron Banana Blog!";

  // useState를 사용하여 state 정의
  const [count, setCount] = useState(0);
  
  const add = () => {
    setCount(count + 1); // setter 함수를 사용해 값 변경
  };

  const remove = () => {
    if (count <= 0) {
      return;
    }
    setCount(count - 1); // setter 함수를 사용해 값 변경 
  };

  return (
    <div>
      <h1>{hello}</h1>
      <h2>Example: state 사용</h2>
      <div style={{ fontSize: 15 }}>Citron을 장바구니에 담겠습니까?</div>
      <button style={{ fontSize: 15, margin: 5 }} onClick={add}>
        add
      </button>
      <button style={{ fontSize: 15 }} onClick={remove}>
        remove
      </button>
      <br />
      <br />
      <br />
      <div style={{ fontSize: 15, color: "blue" }}>
        장바구니에 담긴 Citron 개수: {count}
      </div>
    </div>
  );
};

export default App;

 

2. Object, Array를 State로 사용할 경우

  • Object 또는 Array와 같이 내부 element를 갖는 자료구조를 State로 정의한다면 내부 element의 변화를 관리하는 것이 일반적인 목적일 것이다.
  • 이 때 기존에 정의한 객체(Object, Array)에 element를 수정 후 setter 함수를 이용하는 것이 아닌, 반드시 새로운 객체를 정의한 후 setter 함수를 이용해야 함에 주의하자.

Wrong Case

  • changeName 함수를 보면 기존에 생성된 객체인 person의 name 속성의 값을 "Banana"로 수정하고 setter 함수인 setPersonState를 사용했다. 하지만 실제로 change 버튼을 눌러보면 Citron은 Banana로 변경되지 않는다. 
  • 이유는 React가 Re-rendering이 되기 위해선 state 자체가 변경되어야 하는데, state 자체는 person이라는 객체이고 person의 name 속성 값이 변경되었을 뿐 person 객체는 그대로이기 때문에 Re-rendering하지 않는 것이다.
  • 따라서 새로운 객체를 정의해 name을 변경한 후 setPersonState을 사용해야한다. → Correct Case
import React, { useState } from "react";

const App = () => {
  const person = {
    name: "Citron",
    age: 20,
  };
  const [personState, setPersonState] = useState(person);

  const changeName = () => {
    person.name = "Banana"; // 기존 객체 person에 값을 수정한 후
    setPersonState(person); // setter 함수를 사용 → 화면 변화 없음
  };

  return (
    <div>
      <h1>Name을 Banana로 변경</h1>
      <div>
        <button onClick={changeName}>change</button>
        <h2>{personState.name}</h2>
      </div>
    </div>
  );
};

export default App;

 

Correct Case

  • 새로운 Object 타입의 객체 newPerson을 생성한 후 name 속성을 수정해 setPersonState 함수를 사용했다.
  • 화면에서 Citron → Banana로 잘 변경되는 것을 확인할 수 있다.
import React, { useState } from "react";

const App = () => {
  const person = {
    name: "Citron",
    age: 20,
  };
  const [personState, setPersonState] = useState(person);

  const changeName = () => {
    const newPerson = { person }; // 새로운 객체 newPerson을 정의
    newPerson.name = "Banana"; // name 속성에 "Banana" 할당
    setPersonState(newPerson); // 새로운 객체를 setter 함수에 사용 → Re-rendering
  };

  return (
    <div>
      <h1>Name을 Banana로 변경</h1>
      <div>
        <button onClick={changeName}>change</button>
        <h2>{personState.name}</h2>
      </div>
    </div>
  );
};

export default App;

 

클래스형 컴포넌트에서 state 사용

레거시 코드에는 여전히 클래스형 컴포넌트에서 state를 사용한 흔적이 남아 있을 수 있으므로 학습한다.

1. 문법

  • state는 Object 타입이다. 
  • state 변경 함수: setState()
    • state에 정의한 변수의 값을 수정할 땐 setState()를 사용한다. setState()를 호출하면 React는 state를 refresh하고 render function을 호출해 UI를 Rerendering하게 된다.
    • state값을 사용할 땐 가 아닌 = 기호를 써서 값을 직접 수정하면 안된다. 값을 직접 수정하면 React는 render 함수를 refresh 하지 않기 때문이다.
    • setState() 내에서 state 값을 변경할 땐 1) state 객체에 직접 접근 방식과 2) Callback 함수 방식 두 가지가 있다. 1번은 setState 함수 외부를 참조하는 방식이므로 2번 Callback 함수를 사용하는 것이 권장된다
    • state에 없는 속성(예시 코드에서 count가 아닌 속성)을 setState() 내에 작성할 경우 state에 해당 속성이 추가된다
  • 예시 코드
import React, { Component } from "react";

class App extends Component {
  hello = "Welcome to Citron Banana Blog!";

  // state 정의
  state = {
    count: 0,
  };

  // state.count += 1
  add = () => {
    // setState() 이용하여 state 변경: 1) state 객체에 직접 접근
    this.setState({ count: this.state.count + 1 }); 
  };

  // state.count -= 1
  remove = () => {
    if (this.state.count > 0) {
      // setState() 이용하여 state 변경: 2) callback 함수를 이용. 이 때 current는 this.state
      this.setState((current) => ({ count: current.count - 1 }));
    }
  };

  render() {
    return (
      <div>
        <h1>{this.hello}</h1>

        <h3>Example: state 사용</h3>
        <div>Citron을 장바구니에 담겠습니까?</div>
        <button onClick={this.add}> add </button> // add 버튼 클릭시 add 함수 호출
        <button onClick={this.remove}> remove </button> // remove 버튼 클릭시 remove 함수 호출
        <div>----------------------------------</div>
        <div>장바구니에 담긴 Citron 개수: {this.state.count}</div>
      </div>
    );
  }
}

export default App;

 

 

[Reference: Official Document]

State and Lifecycle: State 설명

https://ko.reactjs.org/docs/state-and-lifecycle.html

Using the State Hook: State 사용

https://ko.reactjs.org/docs/hooks-state.html

Hook의 개요: React Hook 개발 이유 및 클래스 컴포넌트의 문제점 극복

https://ko.reactjs.org/docs/hooks-intro.html