프로젝트/정글 FE 스터디

[React] Preserving and Resetting State

KimCookieYa 2023. 10. 16. 09:31

state 보존 및 재설정

state는 컴포넌트 간에 격리된다. React는 UI 트리에서 어떤 컴포넌트가 어떤 state에 속하는지를 추적한다. React는 state를 언제 보존하고 언제 초기화할지를 제어할 수 있다.

학습 내용

  • React가 컴포넌트 구조를 “보는” 방법
  • React가 state를 유지하거나 재설정하도록 선택할 때
  • React가 컴포넌트의 state를 재설정하도록 강제하는 방법
  • key와 type이 state 보존 여부에 영향을 미치는 방법

UI 트리

  • 브라우저는 UI를 모델링하기 위해 "여러 트리 구조"를 사용한다.
  • DOM은 HTML element
  • CSSOM은 CSS
  • Accessibility 트리도 있다!

=> 이처럼, React도 트리 구조를 사용하여 사용자의 UI를 관리하고 모델링한다!

  • React이 JSX(또는 TSX)로부터 UI 트리를 만든다. 그런 다음 React DOM은 해당 UI 트리와 일치하도록 브라우저 DOM을 업데이트한다.

state는 트리의 한 위치에 묶인다.

  • 컴포넌트에 state를 부여할 때
  • 컴포넌트 내부에 state가 존재한다고 생각할 수도 있다.
  • 그러나, 실제로 state는 React 내부에서 유지된다.
  • React는 UI 트리에서 컴포넌트의 위치에 따라 보유하고 있는 각 state를 올바른 컴포넌트와 연결한다.

동일한 위치의 동일한 컴포넌트는 state를 유지한다.

  • React는 컴포넌트가 UI 트리의 해당 위치에서 렌더링되는 동안 컴포넌트의 state를 유지한다.
  • 즉, 컴포넌트가 제거되거나 같은 위치에 다른 컴포넌트가 렌더링되면 React는 해당 컴포넌트의 state를 삭제한다.
    <div>{isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}</div>
  • isFancy가 true이든 false이든, 가 렌더링되는 것은 동일하기 때문에 Counter의 state는 재설정되지 않는다!
  • UI 트리에서의 위치가 같기 때문에 React의 관점에서는 같은 컴포넌트이다.

동일한 위치의 다른 컴포넌트는 state를 초기화한다.

<div>
      {isPaused ? (
        <p>See you later!</p> 
      ) : (
        <Counter /> 
      )}
      </div>
  • UI 트리 상에서 같은 위치에 다른 컴포넌트가 전환되므로, React는 Counter를 제거하고 그 state를 소멸시킨다.
  • 또한, 같은 위치에 다른 컴포넌트를 렌더링하면 전체 하위 트리의 state가 재설정된다.
<div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} /> 
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}</div>
  • 위 컴포넌트가 서로 전환되면 state가 재설정된다.
  • 가 렌더링되더라도 div의 첫 번째 자식이 div에서 section으로 변경되었기 때문에, 자식 div가 DOM에서 제거되면 하위 전체 트리도 함께 제거된다.

컴포넌트를 중첩하지 말자.

import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

  function MyTextField() {
    const [text, setText] = useState('');

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

  return (
    <>
      <MyTextField />
      <button onClick={() => {
        setCounter(counter + 1)
      }}>Clicked {counter} times</button>
    </>
  );
}
  • 이 컴포넌트는 버튼을 입력할 때마다 state가 사라진다.
  • 이는 MyComponent를 렌더링할 때마다 다른 MyTextField 함수가 생성되기 때문이다.
  • 이 문제를 방지하려면 항상 컴포넌트 함수를 최상위 수준에서 선언해야 한다!

동일한 위치에서 state 재설정하기

  • 기본적으로 React는 컴포넌트가 같은 위치에 있는 동안 state를 보존한다.
  • 그러나 가끔 state를 의도적으로 리셋하고 싶을 때가 있다.
  • 컴포넌트를 같은 위치에서 전환할 때, state를 재설정하는 방법은 두 가지가 있다!
  1. 컴포넌트를 다른 위치에서 렌더링하기
{isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
  • 두 카운터를 독립적으로 만들기 위해서는 두 개의 다른 위치에 렌더링하면 된다.
  • 각 카운터의 state는 DOM에서 제거될 때마다 소멸된다.
  • 이 솔루션은 같은 위치에 몇 개의 독립적인 컴포넌트만 렌더링할 때 편리하다.
  1. key로 state 재설정하기
  • 반복문을 사용해서 목록을 렌더링할 때처럼 key를 사용하면 된다.
  • 컴포넌트마다 다른 key를 지정하면 React에게 다른 컴포넌트 임을 알릴 수 있다.
  • key를 지정하면 React가 부모 내순서가 아닌 key 자체를 위치의 일부로 사용하도록 지시한다.
  • 따라서 JSX에서 같은 위치에 렌더링하더라도 React의 관점에서 보면 서로 다른 카운터이다.
  • 단, key는 전역으로 고유하지는 않다! key는 부모 내에서의 위치만 지정한다.

제거된 컴포넌트에 대한 state 보존

  • 제거된 컴포넌트의 state를 보존했다가 복구하고 싶을 때가 있다.
  • 다음의 방법을 사용해보자.
  1. 현재 컴포넌트만 "렌더링"하는 것이 아니라, 모든 컴포넌트를 렌더링하되 다른 모든 컴포넌트를 css로 숨길 수 있다. 컴포넌트는 트리에서 제거되지 않으므로 로컬 state가 유지된다! 간단하지만 숨겨진 트리가 크고 많은 DOM 노드를 포함한 경우 속도가 매우 느려질 수 있다.
  2. 부모 컴포넌트에서 각 자식의 state를 끌어올려서 보관할 수 있다. 가장 일반적인 솔루션이다.
  3. React state 외에 다른 소스를 사용할 수 있다. localStorage 에 값을 쓰고 읽어서 state를 초기화할 수 있다!

Recap

  • React는 동일한 컴포넌트가 동일한 위치에서 렌더링되는 한 state를 유지한다.
  • state는 JSX 태그에 보관되지 않습니다. JSX를 넣은 트리 위치와 연관되어 있다.
  • 하위 트리에 다른 key를 지정하여 강제로 state를 재설정할 수 있다.
  • 컴포넌트 정의를 중첩하지 마세요. 실수로 state가 초기화될 수 있다.