[TIL] useState

·2023년 11월 1일
1

TIL

목록 보기
21/85
post-thumbnail

State

  • State란 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. (데이터가 저장되는 곳)
  • 왜 바뀌어야 할까? -> UI 로의 반영을 위해서!

React에서 값이 바뀔 데이터를 담는 두가지 방법을 비교하며 왜 State를 써야하는지 알아보자.

첫번째 방법 (BAD)

const root = document.getElementById("root");
let counter = 0; // counter라는 변수를 만들었다

function countUp() {
  counter = counter + 1;
}

const Container = () => (
  <div>
    <h3>Total click : {counter}</h3>
    <button onClick={countUp}>Click me!</button>
  </div>
);
ReactDOM.createRoot(root).render(<Container />); // 렌더링 한 번만 실행

이렇게 하면 버튼 클릭시 counter는 증가하지면 아직 UI에 반영되지는 않는다.
여기에 있는 코드 어디에서도 UI를 re-rendering 해주고 있지 않기 때문이다.

우리는 컴포넌트를 한 번만 rendering 하고 있기 때문이다.
즉 UI에 반영하려면 countUp() 을 호출할 때마다 rendering을 또 해주어야 한다.

const root = document.getElementById("root");
let counter = 0;

function countUp() {
  counter = counter + 1;
  ReactDOM.createRoot(root).render(<Container />); // 추가!
}

const Container = () => (
  <div>
    <h3>Total click : {counter}</h3>
    <button onClick={countUp}>Click me!</button>
  </div>
);
ReactDOM.createRoot(root).render(<Container />);

이 방법은 우리가 값을 바꿀 때마다 다시 rendering 하는 것을 잊어서는 안된다는 치명적인 단점이 있다.

두번째 방법 (GOOD)

counter 변수를 생성하는 대신 useState() 함수를 이용해보자!

import React, { useState } from "react";

function App() {
  const data = useState();
  console.log(data); // 콘솔에 찍어보자!
  return <div></div>;
}

export default App;


useState() 를 data라는 변수에 담아 콘솔에 찍어보면 위와 같다.
우리가 useState() 로 우리가 받게 되는 건 undefined함수가 들어있는 배열이다. (길이가 2인)

  • 배열의 첫 번째 요소 : value
  • 배열의 두 번째 요소 : value를 변경할 때 사용하는 function (modifier 함수)

const data = useState('hyewon'); 으로 초기값을 주면 다음과 같이 출력된다.


이 데이터에 접근하려면 어떻게 해야할까?

import React, { useState } from "react";

function App() {
  const data = useState("hyewon");
  const counter = data[0];
  const setCounter = data[1];
  return <div></div>;
}

export default App;

이렇게 할 수도 있겠지만... 더 멋찐 방법이 있다. 구조 분해 할당!

구조분해할당 ?

  • 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식
const food = ["tomato", "potato"];
const [myFavFood, mySecondFavFood] = food;
console.log(myFavFood); // tomato
console.log(mySecondFavFood); // potato
import React, { useState } from "react";

function App() {
  const [counter, setCounter] = useState(0); // 구조분해할당
  return <div></div>;
}

export default App;

이제 useState() 가 넘겨주는 배열의 두번째 요소인 modifier 함수를 이용하여 값을 바꿔보자.
modifier() 함수는 어떤 값을 부여하든 그 값으로 업데이트하고, re-rendering을 일으킨다.
(보통 첫번째 요소는 counter와 같이 원하는 이름을 붙이고, 두번째 요소는 setCounter와 같이 앞에 "set"을 붙인다.)

import React, { useState } from "react";

function App() {
  function onClick() {
    setCounter(counter + 1);
  }
  const [counter, setCounter] = useState(0);
  return (
    <div>
      <h3>Total click : {counter}</h3>
      <button onClick={onClick}>Click me!</button>
    </div>
  );
}

export default App;


버튼 클릭할 때마다 UI에 바로바로 반영이 된다.
(데이터가 바뀔때마다 컴포넌트를 re-rendering하고 UI를 refresh하는 것! -> React의 파워!💪)
버튼 클릭할 때마다 setCounter()라는 modifier 함수로 state를 바꾸게 되고, 이때 새로운 값을 가지고 컴포넌트 전체가 재생성된다. 즉, state가 바뀌면 React 가 새로운 값을 가지고 컴포넌트를 refresh 해준다.
reactJS는 똑똑해서 실제로 바뀌는 값만 판단해서 불필요한 rerendering을 제외한 채로 동작한다!
(<h3>Total click : {counter}</h3> 전체가 리렌더링 되지 않고 {counter} 부분만 리렌더링 된다)

함수형 업데이트

이전의 counter를 이용해서 현재의 counter를 바꾸려면 setCounter() 를 이용해야 한다.
위에서는 setCounter(counter + 1); 과 같이 사용했다.
그러나 counter는 다른 곳에서 변경되어 우리가 생각했던 값이 아니게 될 수도 있어서 별로 좋은 방법은 아니다.
state를 변경하는 방법을 알아보자!

  1. 직접 할당
    • setCounter(counter + 1);
  2. 함수를 할당 (함수형 업데이트)
    • setCounter((current) => current + 1);

둘다 현재의 state를 가지고 새로운 값을 계산해 내지만 2번이 좀 더 안전하다.
2번이 current가 확실히 현재 값이라는 것을 보장하고 있다. 예상치 못한 변경이 어디선가 일어났다 하더라도 혼동을 방지할 수 있다.

// 기존에 우리가 사용하던 방식
setNumber(number + 1);

// 함수형 업데이트 ⭐️
setNumber((currentNumber) => currentNumber + 1);
  • 함수의 인자에서는 현재의 state 를 가져올 수 있고, 함수 안에는 이 값을 변경하는 코드를 작성할 수 있다.

일반 업데이트 방식

// src/App.js

import { useState } from "react";

const App = () => {
  const [number, setNumber] = useState(0);
  return (
    <div>
			{/* 버튼을 누르면 1씩 플러스된다. */}
      <div>{number}</div> 
      <button
        onClick={() => {
          setNumber(number + 1); // 첫번째 줄 
          setNumber(number + 1); // 두번쨰 줄
          setNumber(number + 1); // 세번째 줄
        }}
      >
        버튼
      </button>
    </div>
  );
}

export default App;

함수형 업데이트 방식

// src/App.js

import { useState } from "react";

const App = () => {
  const [number, setNumber] = useState(0);
  return (
    <div>
			{/* 버튼을 누르면 3씩 플러스 된다. */}
      <div>{number}</div>
      <button
        onClick={() => {
          setNumber((previousState) => previousState + 1);
          setNumber((previousState) => previousState + 1);
          setNumber((previousState) => previousState + 1);
        }}
      >
        버튼
      </button>
    </div>
  );
}

export default App;

다르게 동작하는 이유

일반 업데이트 방식은 버튼을 클릭했을 때 첫번째 줄 ~ 세번째 줄의 있는 setNumber가 각각 실행되는 것이 아니라, 배치(batch)로 처리한다. 즉 우리가 onClick을 했을 때 setNumber 라는 명령을 세번 내리지만, 리액트는 그 명령을 하나로 모아 최종적으로 한번만 실행을 시킨다. 그래서 setNumber을 3번 명령하든, 100번 명령하든 1번만 실행된다.
반면에 함수형 업데이트 방식3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번씩 실행시킨다. 0에 1더하고, 그 다음 1에 1을 더하고, 2에 1을 더해서 3이라는 결과가 우리 눈에 보이는 것이다.😀

📢 리액트는 성능을 위해 setState()를 단일 업데이트(batch update)로 한꺼번에 처리할 수 있습니다.
[출처] React 공식문서

공식문서의 설명처럼, 불필요한 리-렌더링을 방지(렌더링 최적화)하기 위해 즉, 리액트의 성능을 위해 한꺼번에 state를 업데이트 하는 것!

profile
느리더라도 조금씩, 꾸준히

0개의 댓글