useState

2023-08-28
ReactHooks

Table of Contents

useState가 나오게 된 배경

  • 클래스형 컴포넌트에서 함수형 컴포넌트로 바뀔 때 클래스형 컴포넌트의 강점인 생명주기(라이프사이클)과 상태값 관리를 Hook이란 개념으로 도입하게 되면서 상태값 관리를 useState라는 Hook으로 관리하게 되었습니다.

1. 프로젝트 세팅 React + TS + Vite

$ npm init vite@latest React-UseState -- --template react-ts
$ cd React-UseState
$ npm i

2. useState 사용해보기

진부한 Counter 예제를 만들어보겠습니다.

/src/App.tsx

import { useState } from "react";
import './App.css';

const App = () => {
  const [count, setCount] = useState<number>(0);

  function onClickHandler(event: React.MouseEvent) {
    event.preventDefault(); // event 동작 방지
    setCount(count + 1);
  }

  console.log("App 컴포넌트 재실행");
  return (
    <div>
      <button onClick={onClickHandler}>count is {count}</button>
    </div>
  );
}

export default App;
  • useState는 두 개의 항목이 포함된 배열을 리턴합니다. 첫번째 항목은 값을 리턴하고, 두번째 항목은 첫번째 항목을 변경할 set함수를 리턴하게 됩니다.
  • set함수의 네이밍컨벤션은 set~~~이고, 활용방법은 set(업데이트 할값)입니다.

동작 순서

  1. 버튼을 누를때마다 onClickHandler가 실행
  2. 실행 문 안의 setCount가 실행되면
  3. 버튼 안에 있는 숫자 count가 하나씩 증가합니다.

의문점

  • setCount가 실행되면 App 컴포넌트 재실행이라는 console.log가 찍히게 됩니다. 함수가 재실행된다는 의미입니다. 왜 재실행 될까요? set 함수는 렌더링을 트리거 할 수 있습니다.

useState에 값을 할당하는 방법

  1. 초기화
const [count, setCount] = useState(0);
  1. Lazy initialization(게으른 초기화)
import React, { useState } from "react";
import "./App.css";

const App = () => {
	 const [count, setCount] = useState<number[]>(
    new Array(2000).fill(0).map((value, index) => {
      console.log(value);
      return value;
    })
  );
  const [anotherCount, setAnotherCount] = useState<number>(0);

  function onClickHandler(event: React.MouseEvent) {
    event.preventDefault();
    setAnotherCount((x) => x + 1);
  }

  console.log("App 컴포넌트 재실행");
  return (
    <div className='card'>
      <button onClick={onClickHandler}>anotherCount is {anotherCount}</button>
    </div>
  );
};

export default App;
  • 버튼을 누르면 anotherCount가 업데이트 되어 count의 값이 재 할당됩니다.
  • 2000번의 배열을 돌고 나서 map에게 반환된 배열이 count에 할당됩니다.

적용 후

import React, { useState } from "react";
import "./App.css";

const App = () => {
	 const [count, setCount] = useState<number[]>(() => {
    return new Array(2000).fill(0).map((value, index) => {
      console.log(value);
      return value;
    });
  });
  const [anotherCount, setAnotherCount] = useState<number>(0);

  function onClickHandler(event: React.MouseEvent) {
    event.preventDefault();
    setAnotherCount((x) => x + 1);
  }

  console.log("App 컴포넌트 재실행");
  return (
    <div className='card'>
      <button onClick={onClickHandler}>anotherCount is {anotherCount}</button>
    </div>
  );
};

export default App;
  • state가 처음 만들어 질 때만 실행됩니다.
  • '비싼 비용의 계산' 이 필요할 때 써야합니다. 단순히 값을 넘기는 비용보다 함수를 쓰기 때문에 값의 크기가 크기 때문입니다.
  • localStorage 의 접근, mapfilter,find 등의 배열을 조작하는 것들이 그 예가 될 수 있습니다. 일반적으로 함수를 통해서 값을 구해야한다면, 이는 비싼 비용이 드는 계산이며 게으른 초기화를 하는게 좋을 수도 있습니다. new Date() 도 해주면 좋습니다.

useState의 값을 업데이트 하는 방법

  • set 함수에 값을 전달하는 방식에 따라 업데이트 하는 방식이 조금 다릅니다.
function onClickHandler(event: React.MouseEvent) {
    event.preventDefault();
    setCount(count + 1); // setCount(0 + 1);
    setCount(count + 1); // setCount(0 + 1);
    setCount(count + 1); // setCount(0 + 1);
  }
  • onClickHandler에 setCount로 count의 값을 next state로 넣어줍니다. 의도는 count의 값을 3 증가시키는거였죠.
  • 하지만 어떤가요? 버튼을 누른 후 버튼 안에 찍힌 값은 3이 증가된 값이 아닌, 1이 증가되었습니다.
  • 함수 안에서 count의 값은 업데이트 되지 않은 값이니 업데이트 되지 않은 값에 1이 증가됩니다.
  • 리액트의 state update 방식은 batch update로 여러개의 setState를 하나의 re-render로 묶습니다.

함수형 업데이트

function onClickHandler(event: React.MouseEvent) {
    event.preventDefault();
    setCount((prev) => prev + 1); // setCount(0 + 1);
    setCount((prev) => prev + 1); // setCount(1 + 1);
    setCount((prev) => prev + 1); // setCount(2 + 1);
  }
  • set 함수에 함수를 넣어주게 되면 이전 값을 가져오게 됩니다.
  • 함수형 업데이트의 prev 값을 처음 불러온다면 렌더링 하기 전 할당된 값이며, 함수형 업데이트로 값을 업데이트를 한다면 prev로 저장된 값을 batch update로 넘겨질 값으로 업데이트 할 수 있습니다.

3. useState의 타입

  • useState에 마우스를 올려놓고 command + 마우스클릭을 해봅시다.

  • React가 useState를 정의하는 타입이 나오게 됩니다.

해석

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
  • useState는 S라는 제네릭 타입을 받고 있고, 파라미터로 S의 타입을 가지고 있는 값을 받습니다. 혹은 함수형태로 S타입의 값을 할당할 수 있습니다.
  • useState는 두개의 타입을 리턴합니다. S의 타입을 가지는 초기값과 Dispatch<SetStateAction<S>>라는 함수를 리턴합니다.

Dispatch

  • 다시 마우스를 놓고, Dispatch 타입을 따라가보면

  • Dispatch는 함수형입니다. A 타입의 value라는 파라미터를 받고, 아무것도 리턴하지 않습니다. 요약하면 A라는 타입의 value로 값을 받을 수 있는 함수입니다.

SetStateAction이 A 타입에 들어갔으니, SetStateAction이란 타입도 알아보겠습니다.

  • SetStateAction 타입은 S 타입의 값을 리턴하게 되고, prevState. 즉, 전에 할당된 S타입의 값을 파라미터로 받고 S타입의 값을 리턴하고 있습니다.

Fiber : https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberHooks.new.js

https://immigration9.github.io/react/2021/06/12/automatic-batching-react.html

https://yceffort.kr/2020/10/IIFE-on-use-state-of-react