본문 바로가기

FrontEnd

[React hooks] 리액트 훅의 원리 : 단지 배열일 뿐

반응형
리액트 훅이 배열임임을 알아봅니다.

저는 새로운 hooks API의 열렬한 팬입니다.
그러나 어떻게 사용해야 하는지에 대한 몇 가지 이상한 제약(odd constraints)이 있습니다.
여기에서는 이러한 규칙의 이유를 이해하는 데 어려움을 겪는 사람들을 위해 새 API 사용에 대해 생각하는 방법에 대한 모델을 제시합니다.

Photo by rawpixel.com from Pexels

훅이 어떻게 동작하는지 알아보기

훅의 규칙

React 핵심 팀이 hooks 제안 문서(hooks proposal documentation)에 설명한
hooks를 사용하기 위해 따라야 한다고 규정하는 두 가지 주요 사용 규칙이 있습니다.
  • 루프, 조건문, 중첩 함수 내에서 Hook을 호출하지 마십시오.
  • React Functions에서만 훅 호출 가능
useHooks은 임포트 구문임

후자는 자명하다고 생각합니다.
함수 컴포넌트에 행위(behavior)을 연결하려면 해당 행위를 컴포넌트와 어떻게든 연결할 수 있어야 합니다.

전자는 이와 같은 API를 사용하여 프로그래밍하는 것이 부자연스러워 보일 수 있기 때문에 혼란스러울 수 있다고 생각합니다.
이것이 제가 오늘 탐구하고자 하는 것입니다.

훅의 상태 관리는 배열에 관한 것입니다

보다 명확한 멘탈 모델을 얻기 위해 hooks API의 간단한 구현이 어떤 모습일지 살펴보겠습니다.
이것은 추측이며 API를 구현하는 방법 중 하나일 뿐입니다.
 API가 내부적으로 작동하는 방식은 아닙니다.

useState를 어떻게 구현할까요?

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

  return (
    <Button onClick={() => setFirstName("Fred")}>Fred</Button>
  );
}
훅 API의 이면에 있는 아이디어는
훅크 함수에서 두 번째 배열 항목으로 반환된 setter 함수를 사용할 수 있으며
해당 setter가 훅에서 관리하는 상태를 제어한다는 것입니다.

그래서 리액트는 뭘하나요?

훅이 React 내에서 내부적으로 어떻게 작동하는지 설명하겠습니다.
다음은 특정 컴포넌트를 렌더링하기 위한 실행 컨텍스트 내에서 작동합니다.
이는 여기에 저장된 데이터가 렌더링되는 컴포넌트 외부의 한 수준에 있음을 의미합니다. 이
상태는 다른 컴포넌트와 공유되지 않지만 특정 컴포넌트의 후속 렌더링에 액세스할 수 있는 범위에서 유지됩니다.

1) 초기화

  • 두 개의 빈 배열 생성: `setters` 및 `state`
  • 커서를 0으로 설정
Initialisation: Two empty arrays, Cursor is 0

2) 초기 렌더링

처음으로 컴포넌트 함수를 실행합니다.
각 useState() 호출은 처음 실행될 때
  • setter 함수(커서 위치에 바인딩됨)를 setters 배열에 푸시한 다음
  • 각 state를 state 배열에 푸시합니다.
First render: Items written to the arrays as cursor increments.

3) 후속 렌더링

각 후속 렌더링 시 커서가 재설정되고 해당 값은 각 배열에서 읽습니다.
Subsequent render: Items read from the arrays as cursor increments

4) 이벤트 처리

각 setter에는 커서 위치에 대한 참조가 있으므로 모든 setter에 대한 호출을 트리거하여 상태 배열의 해당 위치에서 상태 값을 변경합니다.
Setters &ldquo;remember&rdquo; their index and set memory according to it.

나이브한 구현

다음은 그 구현을 보여주기 위한 단순한 예제입니다.

참고: 이 예제는 실제 훅이 동작하는 방식을 나타내지는 않지만
단일 컴포넌트에서 훅이 작동하는 방식을 이해하도록 돕습니다.
이것이 우리가 모듈 레벨 변수들을 사용하는 이유입니다.

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']

왜 훅 호출의 순서가 중요한가

이제 외부 요인(조건)이나 컴포넌트 상태를 기반으로 하는 렌더 주기의 훅 순서를 변경하면 어떻게 될까요?
React 팀이 하지 말라고 할 일을 합시다:
  • 루프, 조건문, 중첩 함수 내에서 Hook을 호출하지 마십시오.
  • React Functions 외부에서 훅을 호출하지 마세요
let firstRender = true;

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>
  );
}
여기 조건부 useState 호출이 있습니다. 이것이 시스템에 미치는 혼란을 봅시다.

잘못된 컴포넌트 첫번째 렌더링

Rendering an extra &lsquo;bad&rsquo; hook that will be gone next render

첫번째 렌더링 결과 firstName, lastName은 문제가 없는 것 같습니다.
다음 렌더링 시 어떤 문제가 발생할까요?

잘못된 컴포넌트 두번째 렌더링

렌더링 사이에 훅을 제거하면 문제가 발생합니다.

By removing the hook between renders we get an error.
이제 상태 저장소가 일관성이 없어짐에 따라 firstName과 lastName이 모두 "Rudi"로 설정됩니다.
React 팀은 사용 규칙을 규정하고 있습니다.  
해당 규칙을 따르지 않으면 데이터가 일관되지 않기 때문입니다.

훅(데이터,핸들러)이 배열에 저장되어 있다고 생각하세요

이제 조건문이나 루프 내에서 `use` 후크를 호출할 수 없는 이유가 명확해졌습니다.
호출 순서는 인덱스(커서) 입니다.
배열 인덱스(커서)를 다루고 있기 때문에 렌더링 내에서 호출 순서를 변경하면 커서가 데이터와 일치하지 않고
use 호출이 올바른 데이터 또는 핸들러를 가리키지 않습니다.
 
트릭은 비즈니스를 관리하는 훅를 일관된 커서가 필요한 배열로 생각하는 것입니다.
이렇게 하면 문제가 없을 것입니다.
(또는 use구문을 import 처럼 생각)



반응형