
학습 대상
- useState를 이미 사용해본 사람
- state의 작동 원리가 궁금한 사람
React는 어떤 state를 반환할지 어떻게 알 수 있을까?
우리가 useState를 호출할때 어떤 state 변수를 참조하는지에 대한 정보를 받지 못한다는 것을 눈치챘는가?
import { useState } from 'react';
const [index, setIndex] = useState(0);
useState에 전달되는 식별자가 존재하지 않는데 react는 어떤 state 변수를 반환할지 어떻게 알 수 있을까? 함수를 파싱하는 것과 같은 마법에 의존하는 것일까?
정답은 아니다.
React에서 간결한 구문을 구현하기 위해 Hooks은 동일한 컴포넌트의 모든 렌더링에서 안정적인 호출 순서에 의존한다.
아래의 규칙을 따르면, Hooks은 항상 같은 순서로 호출되기 때문에 실제로 정상 작동한다.
- 루프, 조건 또는 중첩 함수 내에서 Hooks를 호출하지 마세요.
- React 함수에서만 Hook을 호출하세요.
내부적으로 React는 모든 컴포넌트에 대해 한 쌍의 state 배열을 가지며 렌더링 전에 0으로 설정된 현재 쌍 인덱스를 유지한다. useState를 호출할 때마다 React는 다음 state 쌍을 제공하고 인덱스를 증가시킨다.
이 매커니즘을 자세히 살펴보자.
React Hooks 이해하기
위에서 우리는 두가지 규칙을 말했었는데, 이 규칙이 왜 필요한지 이해하는데 어려움을 겪는 사람들을 위해 설명하려한다.
- 루프, 조건 또는 중첩 함수 내에서 Hooks를 호출하지 마세요.
- React 함수에서만 Hook을 호출하세요.
이 두 규칙중 아래의 규칙은 이해하기 어렵지 않다. 기능적 컴포넌트에 동작을 추가하려면 해당 동작을 컴포넌트와 어떻게든 연결할 수 있어야하기 때문에 너무 당연한 말이다.
그러나 첫번째 규칙은 충분히 혼란스러울 수 있기 때문에 오늘 탐구하려 한다.
Hooks의 상태관리는 배열에 관한 것이다.
보다 명확한 이해를 위해 Hooks API의 간단한 구현을 해보려 한다.
이는 추측일 뿐이며 API를 어떻게 생각하고 싶은지 보여주기 위해 API를 구현하는 유일한 방법이다. 이것이 반드시 API가 내부적으로 작동하는 방식은 아니다.
useState()는 어떻게 구현될까?
state Hook이 어떻게 작동하는지 예제를 통해 알아보자.
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
useState() Hooks API의 기본 개념은 Hooks 함수에서 두 번째 배열 항목으로 반환된 setter 함수를 사용할 수 있고 해당 setter가 Hooks에 의해 관리되는 상태를 제어한다는 것이다.
React는 이것으로 무엇을 할까?
curser : Hook의 생성된 순서
state : 우리가 setState()를 사용할 때 지정하는 value
setters : 우리가 setState()를 사용할 때 value를 변경할 때 사용하는 함수
아래의 코드는 useState()가 작동하는 방식에 대해 생각해 볼 수 있는 코드이다. (React의 실제 작동 방식과는 다를 수 있다.)
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
// Our component code that uses hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// This is sort of simulating Reacts rendering cycle
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['Fred', 'Yardley']
1) 초기화
curser : 0
setters, state : [] //빈 배열

2) 첫 번째 렌더링
각 useState() 호출은 처음 실행될 때 setter 함수를 배열에 push한 다음 일부 상태를 state에 push한다.

3) 후속 렌더링
렌더링을 할 때마다 curser = 0으로 초기화 되고 해당 값은 각 배열에서 읽힘

4) 이벤트 처리
각 setter에는 curser 위치에 대한 참조가 있어 호출을 하게 되면 setters 배열의 해당 위치에서 state 값의 변경이 일어남

순서가 중요한 이유
위의 사례를 이해 했다면 아래의 코드도 한번 살펴보자.
function RenderFunctionComponent() {
let initName;
if(firstRender){
[initName] = useState("Rudi");
firstRender = false;
}
const [firstName, setFirstName] = useState(initName);
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
코드의 진행은 아래의 그림과 같을 것이다.

만약 후속 렌더링이 진행된다면?

index와 맞지 않는 정보가 저장되어 있음을 알 수 있다.
React 팀은 사용 규칙을 따르지 않을 경우 일관성 없는 데이터가 발생하기 때문에 이를 규정하고 있다.
Hooks에서 배열 집합을 가리키는 커서를 다루기 때문에 렌더링 내에서 호출 순서를 변경하면 curser가 데이터와 일치하지 않고 호출했을 때 올바른 데이터나 핸들러를 가리키지 않게된다. 이것이 바로 조건문이나 반복문 내에서 Hooks를 호출할 수 없는 이유이다.
만약 우리가 Hooks를 사용할 때 일관된 커서가 필요한 배열 집합으로 생각한다면 오류가 발생하는 코드 작성을 막을 수 있을 것이다.
참고
https://react-ko.dev/learn/state-a-components-memory
https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e
'개발 공부 > React-Next.js' 카테고리의 다른 글
Next.js에서 Devtools가 사라지는 문제 해결 (0) | 2024.08.07 |
---|---|
About Next.js (0) | 2024.08.07 |
[React 학습하기] 컴포넌트 순수성 유지 (0) | 2024.08.07 |
[React 학습하기] 컴포넌트에 props 전달하기 (0) | 2024.08.07 |
[React 학습하기] Quick Start (0) | 2024.08.07 |