컴포넌트가 상호 작용의 결과로 화면의 내용을 변경해야 할 때(렌더링해야 할 때)가 있습니다. 이때 필요한 데이터를 저장하는 컴포넌트별 메모리를 State라고 합니다.
왜 이 데이터를 일반 변수에 저장하지 않을까요? 일반 변수(지역 변수)는 렌더링 간에 유지되지 않고, 변수를 변경해도 렌더링이 일어나지 않기 때문입니다.
때문에 저희는 컴포넌트를 새로운 데이터로 업데이트하기 위해 useState 훅을 사용합니다.
useState처럼 "use"로 시작하는 모든 함수는 훅(hook)이라고 부릅니다. 훅의 특징은 다음과 같습니다.
- React가 렌더링 중일 때만 사용할 수 있는 함수입니다.
- 컴포넌트의 최상위 또는 커스텀 훅에서만 호출할 수 있습니다.
useState 훅은 우리가 필요한 두 가지 기능을 제공합니다.
1. 렌더링 간에 데이터를 유지하기 위한 state 변수
2. 변수를 업데이트한 후 React가 컴포넌트를 다시 렌더링하도록 만드는 state setter 함수
const [index, setIndex] = useState(0);
useState는 몇가지 특징이 있습니다.
- 호출 시 const [something, setSomething] 과 같이 선언하는 것이 일반적입니다.
- useState의 인수는 state 변수의 초기값입니다. 따라서 위 예시에서 index의 초기값은 0입니다.
- 컴포넌트가 렌더링될 때마다, 저장한 값을 가진 state 변수와, state 변수를 업데이트하고 렌더링하도록 만드는 state setter 함수가 반환됩니다.
useState 훅은 다음처럼 사용할 수 있습니다.
// 1. useState를 react에서 가져온다.
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
// 2. state 변수와 setter 함수를 선언하고, useState()가 반환하는 배열로 초기화한다.
const [index, setIndex] = useState(0);
// 3. 이벤트 핸들러나 JSX에서 사용한다.
function handleClick() {
setIndex(index + 1);
}
return (
<>
<button onClick={handleClick}>
Next
</button>
<h3>
({index + 1} of {sculptureList.length})
</h3>
</>
);
}
위 코드에서의 useState의 실제 작동 방식은 다음과 같습니다.
1. 컴포넌트가 처음 렌더링됩니다.
- 인수가 0이므로 [0, setIndex]를 반환합니다.
2. 상호작용에 따라 state를 업데이트합니다.
- 버튼 클릭 시 setIndex(index + 1)를 호출합니다.
- index는 0이므로 setIndex(1)입니다.
- 따라서 index는 1이 되고, 렌더링이 실행됩니다.
3. 컴포넌트가 다시 렌더링됩니다.
- React는 index가 1임을 기억하기 때문에 [1, setIndex]를 반환합니다.
하나의 컴포넌트에 여러 개의 state 변수를 가질 수 있습니다. 하지만 두 state 변수가 자주, 함께 변경된다면 하나의 객체 state 변수를 사용하는 것이 좋습니다. ex) 필드가 많은 폼
여러 개의 변수가 있을 경우, React는 useState 호출 순서를 기준으로 각 상태 값과 setter를 연결합니다. React는 모든 컴포넌트에 대해 한 쌍의 state 배열을 가지고 있고, cursor 포인터를 통해 현재 어떤 useState 호출을 처리 중인지 추적합니다.
Hook 호출 시 커서는 0부터 시작하여 순서대로 증가하고, 현재 커서 위치에 해당 Hook의 상태 값이 저장됩니다. 컴포넌트가 리렌더링될 때 커서를 다시 0으로 초기화하며, 훅이 호출될 때마다 커서를 증가시키며 값을 읽어옵니다.
훅이 어떻게 동작하는지에 대해, 핵심 원리는 JavaScript의 Closure 라는 글이 있습니다. 훅의 동작 원리에 대한 글은 추후 따로 분리하여 정리해보도록 하겠습니다.
State는 선언한 컴포넌트에 대해 비공개로 되어 있으며, 부모 컴포넌트는 이를 변경할 수 없습니다. 따라서 동일한 컴포넌트를 두 번 렌더링한다면, 각 인스턴스는 완전히 격리된 state를 가집니다.
import Gallery from './Gallery.js';
export default function Page() {
return (
<div className="Page">
{/* Gallery 안의 state는 해당 인스턴스에 지역적이므로, 두 Gallery의 state는 서로 격리되어 있음 */}
<Gallery />
<Gallery />
</div>
);
}
만약 두 인스턴스가 state를 동기화하도록 만들고 싶다면, 자식 컴포넌트에서 state를 제거하고 공통 부모 컴포넌트에 이를 추가하여 prop으로 전달하는 방법이 있습니다.
참고한 문서
https://ko.react.dev/learn/state-a-components-memory
State: 컴포넌트의 기억 저장소 – React
The library for web and native user interfaces
ko.react.dev
https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e
React hooks: not magic, just arrays
Untangling the rules around the proposal using diagrams
medium.com
https://it-eldorado.com/posts/069bc5ac-7609-43a6-9fd4-f8b8fc84764b
React Hook 동작(구현) 원리 간단히 알아보기 :: IT 엘도라도
요즘에는 React로 개발할 때 클래스를 많이 사용하지 않는 듯하다. React 16.8 버전부터 도입된 Hook API가 상당한 인기를 얻고 있기 때문이다. 필자 역시도 React를 처음 공부할 때는 클래스 컴포넌트
it-eldorado.com
'React' 카테고리의 다른 글
[리액트 공식문서] 상호작용성 더하기 - (3) 렌더링 그리고 커밋 (1) | 2025.01.14 |
---|---|
[리액트 공식문서] 상호작용성 더하기 - (1) 이벤트에 응답하기 (0) | 2025.01.13 |