React - Reconciliation

이소라·2022년 9월 19일
0

React

목록 보기
17/23

Reconciliation

동기

  • state나 props가 갱신되면 render() 함수가 새로운 React element 트리를 반환함
  • 이전 트리를 새 트리로 변환하기 위해 필요한 최소한의 연산 수를 구하는 알고리즘이 필요함
  • React는 아래의 두 가정을 기반으로 O(N) 복잡도틀 가지는 heuristic 알고리즘을 구현함
    1. 서로 다른 타입의 두 element는 서로 다른 트리를 만들어냄
    2. key prop을 통해서, 여러 렌더링 사이에 어떤 자식 element가 변경되지 않는지 알 수 있음



비교 알고리즘 (Diffing Algorithm)

  • 두 개의 트리를 비교할 때, 두 element의 root element부터 비교함
  • root element의 타입에 따라 이후 동작이 결정됨
    • root element의 타입이 다를 경우, 이전 트리를 버리고 새로운 트리를 만듬
    • root element의 타입이 같을 경우, 이전 트리에서 변경된 속성만 갱신함
  • DOM 노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리함

DOM element의 타입이 다른 경우

  • DOM element의 타입이 다르면, React는 이전 트리를 버리고 새로운 트리를 구축함

    • 예) <a> -> <img>, <article> -> <comment>, <button> -> <div> 등 element의 타입이 바뀌면 트리 전체를 재구축함
  • 트리를 버릴 때 이전 DOM 노드들은 모두 파괴됨

    • 컴포넌트 인스턴스는 componentWillUnmout()가 실행됨
  • 새로운 트리가 만들어질 때, 새로운 DOM 노드들이 DOM에 삽입됨

    • 컴포넌트 인스턴스는 UNSAFE_componentWillMount()가 실행되고, componentDidMount()가 이어서 실행됨
  • 이전 트리와 연관된 모든 state는 사라짐


DOM element의 타입이 같은 경우

  • 같은 타입의 두 React DOM element를 비교할 때, React는 두 element의 attributes를 확인하여 동일한 attribute은 유지하고 변경된 attribute만 갱신함
    • 아래 코드에서, React는 현재 DOM 노드 상에 className만 수정함
<div className="before" title="stuff" />
// className attribute가 다름
<div className="after" title="stuff" />
  • 또한 style이 갱신될 때, React는 변경된 속성만 갱신함
    • 아래 코드에서, React는 fontWeight 속성은 수정하지 않고 color 속성만 수정함
<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />

컴포넌트가 갱신될 경우

  • 컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지됨
  • React는 새로운 element의 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 prop을 갱신함
    • 이 때 해당 인스턴스의 UNSAFE_componentWillReceiveProps(), UNSAFE_componentWillUpdate(), componentDidUpdate를 호출함
    • 다음에 render() 메서드가 호출되고 비교 알고리즘이 이전 결과와 새로운 결과를 비교하여 재귀적으로 처리함



자식에 대한 재귀적 처리

  • DOM 노드의 자식들을 재귀적으로 처리할 때, React는 두 리스트를 동시에 순회하고 차이점이 있으면 변경을 생성함
    • 자식 맨 끝에 element를 추가하면, 두 트리 사이의 변경은 마지막에만 하면 됨
    • 자식 맨 앞에 element를 추가하면, 종속 트리는 유지되지만 모든 자식을 변경하므로 비효율적임
// 자식 맨 끝에 element를 추가하는 경우
<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

// 자식 맨 앞에 element를 추가하는 경우
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

Keys

  • 이러한 문제를 해결하기 위해, React는 key attribute를 지원함
    • 자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리를 의 자식들이 일치하는지 확인함
    • 아래 코드에서 React는 '2014' key를 가진 element가 새로 추가되었고, '2015'와 '2016' key를 가진 element를 이동하기만 하면 되는 걸 알 수 있음
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>
  • key는 보통 element가 가지고 있는 식별자나 id를 사용하거나 데이터의 일부에 해쉬를 적용하여 생성해서 사용함
  • key는 형제 사이에서만 유일하면 되고, 전역에서 유일할 필요는 없음
  • 최후의 수단으로 배열의 인덱스를 key로 사용할 수 있지만, 배열이 재배열되는 경우 비효율적으로 동작할 것임
    • 인덱스를 key로 사용하면 항목의 순서가 바뀌었을 때 key도 바뀜
    • 그 결과, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수 있음



고려 사항

  • React는 heuristic 알고리즘에 의존하고 있기 때문에, heuristic 알고리즘이 기반하고 있는 가정에 부합하지 않는 경우 성능이 나빠질 수 있음
    1. 알고리즘은 다른 컴포넌트 타입을 갖고 있는 종속 트리들의 일치 관계를 확인하지 않음
      • 매우 비슷한 결과물을 출력하는 두 컴포넌트를 교체하는 경우, 그 둘을 같은 타입으로 만드는 것이 효율적임
    2. key는 반드시 변하지 않고, 예상 가능하며, 유일해야 함
      • 변하는 key(예: Math.random())를 사용하는 경우
        • 많은 컴포넌트 인스턴스와 DOM 노드를 불필요하게 재생성하여 성능이 나빠질 수 있음
        • 자식 컴포넌트의 state가 유실될 수 있음



Fiber Reconciler

기존 Reconciler (React 15)의 약점

  • 기존 reconciliation(stack reconciler)은 재귀적으로 일어나기 때문에, 중간에 함수 호출을 끊을 수 없음
    • React element를 전부 순회할 때까지 자바스크립트의 엔진을 독차지함
    • 이러한 상황에서 애니메이션이 비동기로 동작하면, 애니메이션이 재때 실행되지 못해서 frame이 누락될 수 있음
    • 따라서 stack reconciler는 애니메이션에 약함
      • 데이터에 의한 UI 변경보다 애니메이션에 의한 UI 변경을 우선시할 수 없으므로

React Fiber의 등장

  • Fiber
    • fiber는 한 가상 stack frame을 차지하는 작업의 단위임
    • fiber는 재귀 대신 linked list를 사용함
      • 한 fiber의 작업이 끝나면, 링크를 타고 가서 다음 fiber의 작업을 하면 됨
    • React의 element와 React의 fiber node는 1:1로 대응됨
      • 한 element를 렌더링하는 것을 한 작업 단위인 fiber로 매핑함
    • fiber의 종류는 current fiber와 in progress fiber가 있음
      • current fiber : 이미 렌더링된 것을 나타냄
      • in progress fiber : 현재 stack frame을 차지하며 렌더링이 진행 중인 fiber를 나타냄

Fiber Reconciler (React 16)

  • Fiber reconciler
    • Stack reconciler의 완전히 하위 호환되는 버전임
    • DOM 트리의 노드를 나타내는데 사용되는 fiber로부터 이름이 유래됨
    • 주요 목적은 증가하는 렌더링, UI 애니메이션의 부드러운 렌더링, user interaction과의 반응성임
    • 일을 여러개의 덩어리로 나눠서 여러 개의 frame에 나눠서 렌더링할 수 있게 함
    • 단위별 일에 우선순위를 정의하고, 일을 정지, 재사용, 중지할 수 있게 함



참고

0개의 댓글