React
Reconciliation

Reconciliation에 대해 설명해주세요

reconciliation은 두 단계로 렌더 단계와 커밋 단계로 진행된다.

렌더 단계에서는 컴포넌트를 호출하여 새로운 Virtual DOM 트리를 생성하고 이전 트리와 비교하여 변경 사항을 계산한다.

커밋 단계에서는 파악된 변경 사항을 실제 DOM에 적용한다.

2가지 가정을 기반으로 O(n) 복잡도의 휴리스틱 알고리즘을 사용한다.

  • 서로 다른 타입의 두 요소는 서로 다른 트리를 생성
  • key를 통해 여러 렌더링 사이에서 변경되지 않아야 할 자식 요소 구성 가능

Virtual DOM

React로 무언가를 작성할 때 우리의 목표는 제공하는 모든 것을 적절한 데이터와 함께 화면의 DOM 요소로 변환하는 것이다.

DOM과 DOM에 무언가를 추가하거나 제거하는 속도는 느리다.

const Input = ({ placeholder }) => {
  return <input type="text" id={id} />;
};
 
// somewhere else
<Input placeholder="Input something here" />;
const input = document.getElementById('input-id');
input.placeholder = 'new data';

Virtual DOM이라고 부르는 것을 생성하고 수정한다.

Virtual DOM은 렌더링해야 하는 모든 컴포넌트, 모든 props, children들이 같은 모양의 객체로 구성된 거대한 객체이다.

const Form = () => {
  const [isCompany, setIsCompany] = useState(false);
 
  return (
    <>
      ... // checkbox somewhere here
      {isCompany ? (
        <Input id="company-tax-id-number" placeholder="Enter you company Tax ID" ... />
      ) : (
        <Input id="person-tax-id-number" placeholder="Enter you personal Tax ID" ... />
      )}
    </>
  )
}

React 관점에서 type은 변경되지 않는다.

Input으로 동일한 함수에 대한 참조를 가르킨다.

{
  type: Input, // isCompany: true
}
{
  type: Input, // isCompany: false
}

이 동작이 반드시 옳다는 것은 아니고 리렌더링 대신 다시 마운트시키는 것이 원하는 방식일 수도 있다.

Key의 활용

키를 활용할 수도 있다.

트리의 맨 앞에 요소를 추가하는 것은 성능 면에서 비효율적이다.

해당 코드는 모든 자식을 변경한다.

<ul>
  <li>apple</li>
  <li>banana</li>
</ul>
 
<ul>
  <li>food</li>
  <li>apple</li>
  <li>banana</li>
</ul>

여기서 key를 추가하면 트리의 변환 작업을 효율적으로 수행할 수 있다. 이때 키는 형제 위치에서만 신경써주면 된다.

<ul>
  <li key="001">apple</li>
  <li key="002">banana</li>
</ul>
 
<ul>
  <li key="000">food</li>
  <li key="001">apple</li>
  <li key="002">banana</li>
</ul>

동적 배열과 일반 요소를 같이 사용하는 경우

const data = ['1', '2'];
 
const Component = () => {
 
  return (
    <>
      {data.map((i) => <Input key={i} id={i} />)}
      <!-- will this input re-mount if I add a new item in the array above? -->
      <Input id="3" />
    </>
  )
}

React는 똑똑하게 처리한다.

❎ 착각하기 쉬운 방향

[
  { type: Input, key: 1 }, // input from the array
  { type: Input, key: 2 }, // input from the array
  { type: Input }, // input after the array
];

✅ 실제 처리 방향

[
  // the entire dynamic array is the first position in the children's array
  [
    { type: Input, key: 1 },
    { type: Input, key: 2 },
  ],
  {
    type: Input, // this is our manual Input after the array
  },
];

배열에 Input이 추가되어도 일반 요소에서는 마운트, 언마운트가 발생하지 않는다.

안티 패턴: 컴포넌트 안에 컴포넌트 호출

const Component = () => {
  const Input = () => <input />;
 
  return <Input />;
};

함수는 컴포넌트 내부에서 생성되어 렌더링될 때마다 생성된다.

const a = () => {};
const b = () => {};
 
a === b; // false

부모 컴포넌트가 리렌더링될 때마다 내부의 Input 컴포넌트는 매번 다시 마운트된다.

Fiber

키워드 정리

  • Virtual DOM
  • Key
  • Fiber

참고 자료