김호준
Reconciliation

1장 왜 리액트인가?

양방향(angular) vs 단방향(react)

  • 양방향은 추적이 어렵다
  • 단방향은 상태변화를 명시적으로 이뤄낸다.
  • 단방향은 코드의 규모가 증가하는 단점이 있다.

reconciliation

  • 불필요한 렌더링 생략
  • 리액트에서는 컴포넌트 간에 state는 격리되고 ui 트리에서 어떤 컴포넌트가 어떤 state에 속하는지를 추적한다. state를 언제 보존하고 언제 초기화할지 제어할 수 있다.
  • 브라우저의 렌더 트리(DOM, CSSOM)처럼 리액트도 트리 구조를 사용하여 UI를 관리하고 모델링한다.
    • JSX로 UI 트리를 만들고 React DOM은 해당 UI트리와 일치하도록 브라우저 DOM 엘리먼트를 업데이트 한다.

state는 두 개의 개별 카운터이고 각 트리에서 고유한 위치에 렌더링된다. 즉, 각 컴포넌트는 완전히 분리된 state를 가진다.

import { useState } from "react";
 
export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </div>
  );
}
 
function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
 
  let className = "counter";
  if (hover) {
    className += " hover";
  }
 
  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>Add one</button>
    </div>
  );
}

리액트가 컴포넌트를 제거하면 그 state가 사라진다.

const [showB, setShowB] = useState(true);

리액트 입장에서 보면 같은 컴포넌트

  • 동일한 "주소"를 가진다.
import { useState } from "react";
 
export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? <Counter isFancy={true} /> : <Counter isFancy={false} />}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={(e) => {
            setIsFancy(e.target.checked);
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}
 
function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
 
  let className = "counter";
  if (hover) {
    className += " hover";
  }
  if (isFancy) {
    className += " fancy";
  }
 
  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>Add one</button>
    </div>
  );
}

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

<div>{isPaused ? <p>See you later!</p> : <Counter />}</div>

또한 같은 위치의 다른 컴포넌트를 렌더링하면 전체 하위 트리의 state가 재설정된다.

  return
    <div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} />
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}

그렇기 때문에 컴포넌트 함수 정의를 중첩해서는 안된다.

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는 유지된다. 하지만 때로는 컴포넌트의 state를 리셋하고 싶을 때는??

유지되는 state

  • 다른 위치에 렌더링 하면 된다.
 const [isPlayerA, setIsPlayerA] = useState(true);
  return
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}

재설정되는 state

return
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
  • 더 일반적인 방법, key 사용
{
  isPlayerA ? (
    <Counter key="Taylor" person="Taylor" />
  ) : (
    <Counter key="Sarah" person="Sarah" />
  );
}

text state 를 초기화해야 하는 채팅 앱

  • key를 추가

만약에 이전 수신자를 다시 선택할 때 입력 state를 불러오고 싶으면?

  • css
  • state 끌어올리기
  • localStorage