준영이의 성장일기(FrontEnd)

React 공식문서 도장깨기(1) - useState 본문

프론트엔드/React.js

React 공식문서 도장깨기(1) - useState

Jay_Jung 2024. 8. 21. 17:38

 

리액트 공식문서를 주에 3회, 적어도 2회는 정리하는 시간을 가지자라고 개인적인 목표를 세웠다. 프로젝트를 하면서 리액트에 대해서 다시 복습 겸 좀 더 공부해야하는 필요성을 느꼈고 필요성을 느낀 순간부터 바로 목표를 세우게 되었다. 참조 레퍼런스는 리액트 공식문서 이고 다음과 같다. 

 

https://ko.react.dev/reference/react

 

리액트(React) 참조 개요 – React

The library for web and native user interfaces

ko.react.dev

 

 

<useState>

개념: useState는 컴포넌트에 state 변수를 추가할 수 있는 React Hook이다. 

 

✅ useState는 정확히 두 개의 값을 가진 배열을 반환한다. 차례대로 현재 state 그리고 상태를 업데이트하고 리렌더링을 촉발하는 set 함수로 구성되어 있다.

 

useState는 일종의 Hook 이므로 컴포넌트의 최상위 레벨이나 직접 만든 Hook에서만 호출할 수 있으며 반복문/조건문에서 또한 호출할 수 없다.

<출처: 리액트 공식문서> 

공식문서에 나와있는 개념

 

✅ initialState인 28은 초기 렌더링에만 사용되고 이후에는 무시된다.

const [age, setAge] = useState(28);

 

✅ set 함수

 

setName('junyoung')에서 junyoung은 nextState를 의미한다. 즉 이전의 state를 다음 state로 업데이트 해주는 것이다. nextState로 함수 또한 전달할 수 있는데 특징은 다음과 같다.

 

 

  • 함수 전달: 상태 업데이트 시 함수를 nextState로 전달하면, React는 이 함수를 업데이터 함수로 취급한다.
  • 업데이터 함수의 특성:
    • 이 함수는 순수 함수여야 한다.(동일한 결과를 반환해야 한다)
    • 이 함수는 대기 중인 이전 state유일한 인수로 사용해야 한다.
    • 이 함수는 다음 state를 반환해야 한다.
  • 업데이터 함수 대기열: React는 이 업데이터 함수를 대기열에 넣는다.
  • 컴포넌트 리렌더링: React는 컴포넌트를 리렌더링한다.
  • 대기열 처리: 다음 렌더링 중에, React는 대기열에 있는 모든 업데이터 함수를 적용하여 다음 state를 계산한다.

 

<중요>

 

다음 렌더링에 대한 state 변수만 업데이트한다. set 함수를 호출한 후에도 state 변수에는 여전히 호출 전 화면에 있던 이전 값이 담겨있기 때문에 console을 출력하면 호출 전 상태가 출력된다. setName('junyoung')으로 업데이트 하기 전 상태가 만약 junyoung2라면 console을 찍을 경우 junyoung2가 출력된다. junyoung인걸 확인할려면 return문에서 {name}을 통해서 확인할 수 있다. 

 

const [name, setName] = useState("junyoung2")

function handleClick() {
  setName('junyoung');
  console.log(name); // 아직 "junyoung2"
}

return (
  <div>
    {name} //junyoung 출력
  </div>
)

 

 

import { useState } from "react";
import "./App.css";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("junyoung2");

  function handleClick() {
    setName("junyoung");
    console.log(name); // 아직 "junyoung2"
  }

  return (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>

        <img
          src={reactLogo}
          className="logo react"
          alt="React logo"
          onClick={handleClick}
        />
      </div>
      <h1>Vite + React + {name}</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
  );
}

export default App;

 

React는 state 업데이트를 batch 한다. 모든 이벤트 핸들러가 실행되고 set 함수를 호출한 후에 화면을 업데이트한다. 이렇게 하면 단일 이벤트 중에 여러 번 리렌더링 하는 것을 방지할 수 있는 장점이 있다. 식당에서 주문하는 과정을 예로 들 수 있는데  식당에서 주문을 할 때 웨이터는 주문이 끝날 때까지 기다렸다가 다음 테이블로 넘어간다. 이처럼 batching라고도 하는 이 동작은  여러 컴포넌트에서 나온 다수의 state 변수를 업데이트할 수 있기 때문에 성능 상 이점을 발생시킨다.

 

공식문서 예시 사진

 

✅ 이전 state를 이용하여 state 업데이트 시키기

 

이 코드의 결과값은 어떻게 나올까? 위에서 나온 개념에 의거하면 쉽게 맞출 수 있다.

const [age, setAge] = useState(42)

function handleClick() {
  setAge(age + 1); 
  setAge(age + 1);
  setAge(age + 1);
}

 

set 함수를 호출해도 이미 실행 중인 코드에서 age state 변수가 업데이트되지 않기 때문에 43이 3번 나오게 된다. 이를 해결할려면 업데이터 함수를 전달하여 이전 state를 이어받아 다음 state로 바뀔 수 있도록 구현할 수 있다.

const [age, setAge] = useState(42)

function handleClick() {
  setAge(a => age + 1); 
  setAge(a => age + 1);
  setAge(a => age + 1);
}

 

 

✅ 초기 state 다시 생성하지 않기

React는 초기 state를 한 번 저장하고 다음 렌더링부터는 이를 무시한다. 함수의 경우에도 해당 원리를 적용할 수 있는데 useState의 초기값으로 초기화 함수를 설정해주면 된다. 함수를 useState에 전달하면 React는 초기화 중에만 함수를 호출

한다. (ex. useState(createInitialTodos)); -> 이를 지연 초기화 함수라고 한다.

 

즉, 이 함수의 반환값이 상태의 초기값으로 설정된다. 함수가 매번 실행되지 않고 초기 렌더링 시에만 실행되기 때문에 성능상 많은 이점을 가지고 있다.  만약 createInitialTodos()로 초기값을 설정한다면 다음과 같은 문제점이 있다.

 

 

  • 즉시 실행: useState에 createInitialTodos()처럼 함수 호출을 전달하면, 이 함수가 즉시 실행되어 반환값이 useState의 초기값으로 설정된다.
  • 매번 실행: 이 방식에서는 컴포넌트가 렌더링될 때마다 createInitialTodos() 함수가 실행되는데 이는 초기값을 계산하는 함수가 항상 호출된다는 의미이다.(성능 상에서 단점 존재)

 

 

 

✅ 리렌더링이 너무 많이 발생하는 경우

Too many re-renders. React limits the number of renders to prevent an infinite loop.

 

해당 경우는 첨부된 이미지에서 첫번째 경우에 속한다. 이 경우는 버튼이 렌더링 될 때 즉시 handleClick()이 호출하게 되는데 onClick 속성에 전달된 것은 함수 handleClick의 반환값이지 함수 자체가 아니기 때문에 클릭 시 아무런 동작도 발생하지 않고 리렌더링 에러가 발생한다. 

 

공식문서 참고

 

또한 이런 경우도 존재한다.

 

 

✅ state를 변화하는 함수가 있다면 반복해서 리렌더링 하게 된다. setState는 콜백으로 다시 렌더링을 트리거하기 때문에 위에 서술한 에러가 발생한다. 이 경우 클릭했을 때만 상태가 변경되도록 화살표 함수를 사용해서 해결할 수 있다.

onClick = {() => setHeaderSet(true}