useState 공식문서 읽기 (Hook동작)

서성원·2025년 1월 6일
1

리액트

목록 보기
22/26
post-thumbnail

들어가며

useState는 개발할 때 안 쓰이는 곳이 없을 정도지만 어떤 원리로 동작하는지 아직 잘 모른다는 생각이 들었어요. 그래서 React 공식문서를 통해 useState의 동작 원리에 대해 알아보려고 해요.

useState

State를 통해 컴포넌트는 정보를 기억해요. 개발 중 많이 쓰는 예시로는 입력폼의 입력값을 저장하거나, 이미지 캐러셀에서 슬라이드를 하면 다른 이미지로 업데이트 되어야 하는 경우가 있어요. 컴포넌트는 현재 입력값이나 이미지를 기억해야 하고 React에서는 이 메모리를 state라고 합니다.

일반 변수로 충분하지 않다

아래는 조각상 이미지를 렌더링하는 컴포넌트입니다. "Next"버튼을 클릭하면 index를 1에서 2로 변경해 다음 조각상을 표시해야 합니다. 하지만 작동하지 않습니다.

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

👿 동작하지 않는 이유

handleClick이벤트 핸들러가 지역 변수 index를 업데이트해요. 지역 변수를 업데이트 하는 건 좋은 방법이 아니에요.

지역 변수는 렌더링 간에 유지되지 않습니다. React는 컴포넌트를 두 번째로 렌더링할 때 지역 변수의 변경 사항을 고려하지 않아요. 그리고 지역 변수를 변경해도 렌더링을 일으키지 않습니다. 새로운 데이터로 컴포넌트를 재렌더링한다는 것을 모르는 거죠.

🤔 어떻게 동작시키지?

이 두 가지를 기억하세요.

  1. 렌더링 사이에 데이터를 유지해야 합니다.
  2. 새로운 데이터를 인식해 컴포넌트를 재렌더링 할 수 있어야 합니다.

이것을 가능하게 하는 것이 바로 useState입니다.

1, 렌더링 간 데이터 유지에 필요한 state 변수
2. 변수를 업데이트하고 다시 재렌더링하도록 하는 state setter 함수

state 변수

const [index, setIndex] = useState(0);

index는 state변수, setIndex는 setter함수입니다.

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

useState를 사용해 기존코드를 정상적으로 동작하도록 했어요. 이제는 next 버튼을 클릭하면 조각상 이미지가 전환됩니다.

잠깐 Hook이 뭐지?

Hook은 use로 시작하는 모든 함수를 말해요.

훅은 오직 렌더링 중에만 사용할 수 있어요. 컴포넌트 최상위 수준 또는 커스텀 훅에서만 호출할 수 있습니다. 조건문이나 반복문에서는 사용할 수 없다는 거죠. 무조건적인 선언으로 생각하면 편할 것 같습니다.

useState 해부하기

useState를 호출하는 것은, 이 컴포넌트가 무언가를 기억하길 원한다는 거에요.

const [index, setIndex] = useState(0);

여기선 index를 기억하길 원해요.

useState 동작순서

1. 컴포넌트 최초 렌더링

index 초기값으로 0을 전달했기에 [0, setIndex]를 반환해요. React는 0을 최신 state 값으로 기억합니다.
2. state 업데이트

사용자가 버튼을 클릭하면 setIndex(index+1)를 호출해요. index가 0이었기에 setIndex(1)되고, React는 index가 1임을 기억하고 다시 렌더링을 유발합니다.

3. 컴포넌트 재렌더링
여전히 useState(0)을 보지만, index가 1로 업데이트 된 것을 기억하므로 이번에는 [1, setIndex]를 반환합니다. 이제 1~3번을 계속 반복합니다.

여러개의 state 변수

한 컴포넌트 내 여러 state 변수를 사용할 수 있어요.

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

여기선 indexshowMore 두 가지 state 변수를 가지고 있어요. 여기선 서로 연관이 없기에 두 가지 state 변수를 가지는 게 가능합니다. 하지만 동시에 변경해야 하는 경우는 합치는 게 더 나은 방법이 될 거에요.

index 변경 시 showMore이 false로 변경되어야 하거나, 리렌더링을 최적화하는 경우가 있을 것 같네요.

React가 어떤 state를 반환하는 지 알까?

useState는 어떤 state를 참조하는지에 대한 정보를 받지 못합니다. 전달되는 식별자가 없는데 어떤 변수를 반환하는지 어떻게 알 수 있을까요?

간결한 구문을 위해 동일한 컴포넌트의 모든 렌더링에서 안정적인 호출 순서에 의존합니다. 최상위 수준에서 훅이 호출되기에, 훅은 항상 같은 순서로 호출되고 실제로 잘 동작합니다.

내부적으로 React는 모든 컴포넌트에 대해 한 쌍의 state 배열을 가집니다. 렌더링 전에 0으로 설정된 인덱스 쌍을 유지합니다. 그리고 useState를 호출할 때마다, React는 다음 state쌍을 제공하고 인덱스를 증가시킵니다. 해당 매커니즘에 대해 세부적으로 들어가보겠습니다. 해당 매커니즘은 React Hooks: 마법이 아니라 배열일 뿐를 참고하였습니다.

Hook 작동 방식

✨Hook 규칙
React 에서는 Hooks를 사용하기 위해 반드시 따라야 하는 두 가지 주요 사용 규칙을 규정하고 있습니다.

1. 루프, 조건 또는 중첩된 함수 내부에서 Hooks 호출은 안 된다.
2. React 함수에서만 호출할 수 있다.

전자는 API로 프로그래밍하는 것이 자연스럽지 않기에 혼란을 줄 수 있습니다. 그럼 Hooks API의 간단한 구현이 어떤 것인지 알아볼게요.

function RenderFunctionComponent() {
  const [firstName, setFirstName] = useState("Rudi");
  const [lastName, setLastName] = useState("Yardley");

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}

Hook API의 아이디어는 훅 함수에서 두 번째 배열의 항목으로 반환된 setter 함수를 사용할 수 있고, 해당 setter가 훅에서 관리되는 상태를 제어할 수 있다는 것입니다.

React가 이것을 어떻게 활용할까?

1.초기화

두 개의 빈 배열을 만듭니다. => setters 와 state
커서는 0입니다. 이때, 커서는 현재 배열에서 데이터를 추가하거나 읽을 위치를 얘기합니다.)

초기화 단계에서 배열은 비어 있기에, 커서를 처음 위치로 설정하여 데이터를 삽입하거나 조회할 준비를 한다는 말입니다.

2.첫 렌더링

처음으로 함수를 실행합니다.

처음 실행될 때 각 useState()호출은 커서 위치에 바인딩된 세터 함수를 세터 배열에 푸쉬하고 state는 state 배열에 푸쉬합니다.

3. 이후 렌더링

이후 렌더링 시 커서가 재설정되고 해당 값은 각 배열에서 읽혀집니다.

4. 이벤트 처리

각 세터에는 커서 위치에 대한 참고가 있습니다. 세터에 대한 호출을 트리거하면 상태 배열의 해당 위치에서 상태 값이 변경됩니다.

마치며

리액트 훅의 구조와 동작 순서를 정리해보았습니다. 리액트에는 useState뿐만 아니라 여러 훅이 존재합니다. 다음번에는 각 훅의 역할과 동작에 대해 좀 더 알아볼 예정입니다.

profile
FrontEnd Developer

0개의 댓글

관련 채용 정보