정답: "toggle color" 버튼을 눌러도 count 값은 유지된다.

0

같은 위치에서의 동일한 컴포넌트는 상태가 유지된다.


리액트에서 useState 등을 통해 상태를 정의하면, 그 정보는 어디에 저장되는가? 우리는 이미 4번 문제에서 리액트를 직접 구현해보며 답을 찾았다. 상태는 컴포넌트가 아닌 React 안에 저장된다. 그래야 여러 번의 렌더링 사이에서도 상태가 초기화되지 않고 유지될 수 있었다.

우리는 컴포넌트가 하나 밖에 없는 경우를 구현하였지만, 실제로는 여러 컴포넌트들이 각각의 상태를 가질 수 있다. 리액트는 렌더 트리에서 각 컴포넌트들의 위치를 참고하여 상태값들을 올바른 컴포넌트에 제공한다. 때문에 어떤 컴포넌트의 상태가 리렌더링 이후에도 보존되기 위해서는 두 가지의 조건을 만족해야 한다.

  1. 동일한 컴포넌트
  2. 동일한 위치

여기서 위치는 root를 기준으로 하는 일종의 상대 경로 생각하면 된다. 이번 문제의 코드를 다시 보자.

function App() {
  const [show, setShow] = useState(true);
 
  return (
    <div>
      {show ? <Counter color="red" /> : <Counter color="blue" />}
      <button onClick={() => setShow(!show)}>toggle color</button>
    </div>
  );
}

show가 참이든 거짓이든 상관없이 <Counter/> 컴포넌트는 항상 root의 - 첫 번째 자식(<div>)의 - 첫 번째 자식(<Counter/>)이다. 컴포넌트도 같고, 위치도 같다. 상태는 유지된다.

그러나 다음과 같은 컴포넌트는 상태가 유지되지 않는다. show의 값에 따라 <Counter/>의 위치가 바뀌기 때문이다.

function App() {
  const [show, setShow] = useState(true);
 
  return (
    <div>
      {show && <Counter color="red" />}
      {!show && <Counter color="blue" />}
      <button onClick={() => setShow(!show)}>toggle color</button>
    </div>
  );
}

0

show 값이 true이면 <Counter/> 컴포넌트는 첫 번째 자리에 위치한다. show 값이 false이면 두 번째 자리에 위치한다.

여기서 show 값이 false이면 <Counter color="red" />는 화면에 보이지 않는데, 어떻게 첫 번째 자리를 차지한다는 것일까? 이는 Babel을 통해 변환된 코드를 보면 이해가 쉽다.

function App() {
  const [show, setShow] = useState(true);
 
  return React.createElement(
    "div",
    null,
    show &&
      React.createElement(Counter, {
        color: "red",
      }),
    !show &&
      React.createElement(Counter, {
        color: "blue",
      }),
    React.createElement(
      "button",
      {
        onClick: () => setShow(!show),
      },
      "toggle color",
    ),
  );
}

show 값에 상관 없이 React.createElement 함수의 몇 번째 인자냐에 따라 처음부터 위치가 결정되어 있었다!

위치는 동일하지만 컴포넌트가 바뀌면 어떻게 될까? 이 경우 리액트는 해당 컴포넌트와 그 하위 트리의 모든 상태를 초기화한다. 이를테면 아래의 컴포넌트는 매 클릭마다 <input/>에 입력한 값이 초기화된다. 왜냐하면 매 렌더링마다 다른 <Input/> 컴포넌트가 생성되기 때문이다.

export default function App() {
  const [counter, setCounter] = useState(0);
 
  function Input() {
    const [text, setText] = useState("");
 
    return <input value={text} onChange={(e) => setText(e.target.value)} />;
  }
 
  return (
    <>
      <Input />
      <button
        onClick={() => {
          setCounter(counter + 1);
        }}
      >
        Clicked {counter} times
      </button>
    </>
  );
}

참고자료