React

state, setState

sangchu 2023. 2. 18. 20:27

Understanding State

state는 기본적으로 데이터가 저장되는 곳이다.

아래 코드는 별로 좋지 않은 방법이다.

const root = ReactDOM.createRoot(document.getElementById("root"));
    let counter = 0;
    function countUp() {
      counter += 1;
    }
    const Container = () => (
      <div>
        <h3>Total clicks: {counter}</h3>
        <button onClick={countUp}>Click me</button>
      </div>
    );

   root.render(<Container />); // 렌더링

컴포넌트는 화면 페이지를 처음 로드했을 때 딱 한번 렌더링되므로 그 이후로는 어떤 작업을 해도 UI가 업데이트되지 않는다. 그래서 button을 눌러도 counter 변수 자체는 증가하지만 UI는 계속해서 0을 표시한다.

 

이를 위해서는 리렌더링이 필요하다(render 부분을 다시 호출하고 싶다!)

리렌더링을 하면 해당 컴포넌트도 바뀐 부분만 바뀐 데이터를 가지고 재생성된다. 리렌더링을 하려면 render함수를 만들어서 해당 함수를 호출하면 render가 되도록 하면 된다. 그러고 나면 이제 버튼을 클릭할 때마다 UI가 업데이트 된다.

const root = ReactDOM.createRoot(document.getElementById("root"));
let counter = 0;
function countUp() {
  counter += 1;
  render();
}

function render() {
  return root.render(<Container />);
}

const Container = () => (
  <div>
    <h3>Total clicks: {counter}</h3>
    <button onClick={countUp}>Click me</button>
  </div>
);

render();

하지만 이는 별로 좋지 않은 방법이다.

우리가 값을 바꿀 때마다 render() 함수를 호출해야 하는 것을 잊으면 안되기 때문이다. 만약 이를 호출해야하는 일이 많으면 파일 별로 다시 root를 불러오고 import해야 하는 등 번거러운 일이 많이 생긴다.

 

여기서 잠시 Vanilla.js와 React.js로 각각 짠 코드의 결과를 확인해보면, 버튼을 클릭했을 때 Vanilla.js는 span도 업데이트가 되지만 React.js는 UI에서 바뀐 부분만 업데이트를 해주고 있는 것을 확인할 수 있다.

Vanilla.js
React.js

React의 변경된 부분만 업데이트 되는 게 좋은 이유는,

일반 javascript를 쓴 브라우저는 노드 정보가 바뀔 때마다 노드 트리를 몇 가지 단계를 거쳐 처음부터 다시 생성하지만,

React는 Vitual DOM(가상 DOM)을 써서 우리 시야에 보이는 부분만 수정해서 보여주고 모든 업데이트가 끝나면 일괄로 합쳐서 실제 DOM에 던져준다. 그래서 React가 성능적으로 훨씬 좋다.

 

setState

위 코드 방식은 좋지 않다.

React.js는 어플 내에서 데이터를 보관하고 자동으로 리렌더링을 할 수 있는 useState() 함수를 제공한다.

함수의 괄호 안에 값을 넣으면 초기값을 설정할 수 있다. 아래 함수는 이전에 우리가 했던 방식과 동일하게 작동한다.

const root = ReactDOM.createRoot(document.getElementById("root"));

    const Container = () => {
      const data = React.useState(60);
      console.log(data);
      return (
        <div>
          <h3>Total clicks: {data[0]}</h3>
          <button>Click me</button>
        </div>
      );
    };

root.render(<Container />);

useState()를 호출하면서 생긴 배열의 첫번째 인자는 data, 두번째 인자 f는 data를 바꿀 때 사용하는 함수를 의미한다.

위 코드처럼 작성하면 data를 가져올때 배열의 첫번째 인자를 가져와야하는 불편함이 있다.

배열에서 요소를 꺼내서 간편하게 이름을 부여하는 방법이 있다.

const food = ["tomato", "potato"]
const [myFavFood, mySecondFavFood] = food
console.log(myFavFood) // tomato

// const myFavFood = food[0]; 기존 방식, 하지만 위 방식이 더 간편하다.

이를 useState에 적용하면 다음과 같다.

const root = ReactDOM.createRoot(document.getElementById("root"));

    const Container = () => {
      const [counter, modifier] = React.useState(0);
      return (
        <div>
          <h3>Total clicks: {counter}</h3>
          <button>Click me</button>
        </div>
      );
    };

    root.render(<Container />);

 

왜 modifier가 필요한지 알아보자. modifier함수는 그 값으로 업데이트하고 리렌더링을 일으킨다.

즉, 함수를 호출했을 때 데이터를 건네주고, 그 데이터 값을 변경할 함수도 함께 준다.

그래서 함수를 이용해서 데이터를 바꿨을 때 데이터 값이 바뀌고 컴포넌트도 동시에 리렌더링 된다.

const root = ReactDOM.createRoot(document.getElementById("root"));

    const Container = () => {
      const [counter, modifier] = React.useState(0);
      const onClick = () => {
        modifier(23121);
      };
      return (
        <div>
          <h3>Total clicks: {counter}</h3>
          <button onClick={onClick}>Click me</button>
        </div>
      );
    };

    root.render(<Container />);

위 코드는 버튼을 누르면 modifier 함수 소괄호 안에 넣은 값으로 리렌더링을 한다.

보통 setState에서 이름을 지을 때 두번째 인자(함수)는 [set첫번째 인자명]으로 짓는다. 따라서 modifier를 setCounter로 작성하자.

const root = ReactDOM.createRoot(document.getElementById("root"));

    const Container = () => {
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(counter + 1);
      };
      return (
        <div>
          <h3>Total clicks: {counter}</h3>
          <button onClick={onClick}>Click me</button>
        </div>
      );
    };

    root.render(<Container />);

이제 버튼을 누를 때마다 counter가 1씩 증가하고, UI도 동시에 업데이트된다.

다시 말해 setCounter함수로 state, 즉 어플리케이션의 데이터를 변경할 때 컴포넌트가 재생성된다(새로운 값을 가지고 리렌더링 된다).

컴포넌트가 재생성된다는 것은, Container 컴포넌트가 다시 실행된다는 뜻으로 return도 한번 더 실행된다는 말이다.

하지만 컴포넌트가 다시 실행될 때(리렌더링 될 때) 이벤트리스너나 태그 등을 다시 만드는게 아니라 오로지 달라지는 부분은 바뀐 데이터 부분뿐이다.

표시된 부분(컴포넌트)이 처음 render됐을 때 실행되고, 버튼을 눌렀을 때도 리렌더링 됨을 아래 콘솔을 통해 확인할 수 있다.

 

State Functions

위 방법에서 counter는 다른 곳에서 업데이트 될 수 있으므로 좋은 방법은 아니다. 결과가 예상과 다르게 나올 수 있다.

state를 바꾸는 2가지 방법

  1. 우리가 원하는 값 직접 넣기 (ex. setCounter(100))
  2. 이전 값을 이용해 현재 값을 계산해내는 것  (ex. setCounter(counter + 1))

현재 값을 가지고 계산해야 한다면 함수를 넣는게 좋다.

const onClick = () => {
        // setCounter(counter + 1);
        setCounter((current) => current + 1);
      };

첫 인자는 현재 값, return값이 새로운 state이다.

이전 방식은 동일하게 작동하지만(현재의 state를 가지고 새로운 값 계산) 화살표 함수 방식이 더 안전하다. current가 확실히 현재 값이라고 보장하고 있다.

 

아래 예시로 다시한번 살펴보자.

// 1. 값으로 여러번
function onClick() {
setCounter(counter + 1);
setCounter(counter + 1);
setCounter(counter + 1);
}

// 2. 함수로 여러번
function onClick() {
setCounter(current => current+ 1);
setCounter(current => current+ 1);
setCounter(current => current+ 1);
}

1번은 setCounter를 몇번을 호출해도 한 번만 실행되는 걸 볼 수 있고(counter가 1만 증가), 2번은 setCounter가 호출된 만큼 counter가 증가된 걸 볼 수 있다.

따라서 이전 state값을 기준으로 state값을 설정해야 한다면 setState((state)=> state+1)을 사용하는 것이 안전하다.

이에 대해서는 '비동기'에 대해 알아보면 이해가 될 것이다.

 

 

 

참고 : NomadCoders - ReactJS로 영화 웹 서비스 만들기 강좌

'React' 카테고리의 다른 글

props, PropTypes, memo  (0) 2023.02.18
useState를 이용해 단위변환 기능 만들기  (0) 2023.02.18
[JSCODE] React 3회차 미션  (1) 2023.02.17
[JSCODE] React 2회차 심화미션  (1) 2023.02.14
[JSCODE] React 2회차 미션  (2) 2023.02.14