리액트 고급 가이드 - Reconciliation
개요
리액트는 명명하는?! api를 제공한다. 그래서 모든 업데이트에 대한 변화에 걱정할 필요가 없다 걱정을. 이것은 어플리케이션을 쓰기 엄청 쉽게 만든다 , 그러나 그것이 어떻게 리액트에서 구현되는지 분명하지 않다. 이 문서는 리액트의 'diffing' 알고리즘으로 우리가 만든 선택지를 설명하고 그래서 컴포넌트 업데이트는 충분히 빠른 하이-퍼포먼스 앱으로써 존재하며 업데이트에 대해 예상가능하다
동기
render () 함수를 한 시점에서 React 컴포넌트의 트리를 만드는 것으로 생각할 수 있다. 다음으로, state 또는 props 업데이트 되면 render () 함수는 다른 React 컴포넌트 트리를 반환한다. React는 가장 최근의 트리와 일치하도록 UI를 효율적으로 업데이트하는 방법을 찾아야한다.
하나의 트리를 다른 트리로 변환하기위한 최소 수의 연산을 생성하는 알고리즘 적 문제에 대한 일반적인 해결책이 있다. 그러나 최첨단 알고리즘은 O(n^3)의 복잡성을 가지며, 여기서 n은 트리의 요소 수 이다.
React에서 이것을 사용하면 1000 개의 요소를 표시하는 데 10 억 번의 조회가 필요하다. 이것은 너무 비싸다. 대신, React는 두 가지 가정에 따라 경험적 O(n) 알고리즘을 구현한다.
리액트는 명명하는?! api를 제공한다. 그래서 모든 업데이트에 대한 변화에 걱정할 필요가 없다 걱정을. 이것은 어플리케이션을 쓰기 엄청 쉽게 만든다 , 그러나 그것이 어떻게 리액트에서 구현되는지 분명하지 않다. 이 문서는 리액트의 'diffing' 알고리즘으로 우리가 만든 선택지를 설명하고 그래서 컴포넌트 업데이트는 충분히 빠른 하이-퍼포먼스 앱으로써 존재하며 업데이트에 대해 예상가능하다
동기
render () 함수를 한 시점에서 React 컴포넌트의 트리를 만드는 것으로 생각할 수 있다. 다음으로, state 또는 props 업데이트 되면 render () 함수는 다른 React 컴포넌트 트리를 반환한다. React는 가장 최근의 트리와 일치하도록 UI를 효율적으로 업데이트하는 방법을 찾아야한다.
React에서 이것을 사용하면 1000 개의 요소를 표시하는 데 10 억 번의 조회가 필요하다. 이것은 너무 비싸다. 대신, React는 두 가지 가정에 따라 경험적 O(n) 알고리즘을 구현한다.
- 다른 유형의 두 element는 서로 다른 트리를 생성
- 개발자는 키 prop를 통해 다음 렌더에서 어떤 자식 element가 안정적인지 알 수 있
실제로 이러한 가정은 거의 모든 실제 사용 사례에 유효하다.
Diffing 알고리즘
두 개의 트리를 비교할 때 React는 먼저 두 개의 루트 element를 비교한다. 동작은 루트 element의 유형에 따라 다르다.
elements of different types
루트 element가 다른 유형을 가질 때마다 React는 오래된 트리를 찢어 버리고 새로운 트리를 처음부터 빌드한다. <a>에서 <img>로 이동하거나 <Article>에서 <Comment>로 이동하거나 <Button>에서 <div>로 이동하면 완전히 재작성된다.
트리를 떼어 내면 오래된 DOM 노드가 파괴된다. component instance는 componentWillUnmount()를받습니다. 새 트리를 작성할 때 새 DOM 노드가 DOM에 삽입된다. component instance는 componentWillMount()를 수신 한 다음 componentDidMount()를 수신한다. 오래된 트리와 관련된 모든 상태가 손실된다.
루트 아래의 모든 component도 마운트가 해제되고 상태가 파괴된다. 예를 들어, diffing :
<div>
<Counter />
</div>
==>
<span>
<Counter />
</span>
이 것은 제거한다 오래된 Counter 그리고 새로운 것으로 re-mount한다.
DOM Elements of the same type
같은 유형의 두 개의 React DOM elements를 비교할 때 React는 두 attribute을 보고 동일한 기본 DOM 노드를 유지하며 변경된 attribute만 업데이트한다. 예 :
<div className="before" title="stuff" />
===>
<div className="after" title="stuff" />
이 두 요소를 비교함으로써 React는 기본 DOM 노드에서 className 만 수정한다는 것을 알고 있다.
style을 업데이트 할 때 React는 변경된 properties만 업데이트한다는 것을 알고 있다. 예 :
<div style={{color: 'red', fontWeight: 'bold'}} />
===>
<div style={{color: 'green', fontWeight: 'bold'}} />
이 두 elements를 변환 할 때 React는 fontWeight가 아닌 색상 style 만 수정한다는 것을 알고 있다.
DOM 노드를 처리 한 후, 그 때 React는 자식에 대해 재귀한다.
Component Elements of the same type
기본적으로, DOM 노드의 자식에 대해 재귀 할 때, React는 동시에 자식들의 목록들을 반복 실행한다 그리고 차이가 있을 때마다 변형을 생성한다.
예를 들어, 자식의 끝에 element를 추가 할 때, 이 두 트리 사이의 변환은 잘 작동한다.
<ul>
<li>first</li>
<li>second</li>
</ul>
===>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React는 두 개의 <li> 첫 번째 </ li> 트리와 일치하고 두 번째 <li> 두 번째 </ li> 트리와 일치하며 <li> 세 번째 </ li> 트리를 삽입한다.
naively 구현하면 처음에 요소를 삽입하면 성능이 떨어진다. 예를 들어,이 두 트리 사이의 변환은 제대로 작동하지 않는다.
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
===>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React는 <li> Duke </ li> 및 <li> Villanova </ li> 하위 트리를 그대로 유지할 수 있다는 사실을 깨닫지 않고 모든 자식을 변이시킨다. 이러한 비효율은 문제가 될 수 있다.
Keys
이 문제를 해결하기 위해 React는 key attribute를 지원. 자식들이 key를 가지고있을 때, React는 그 key를 사용하여 원본 트리의 자식들을 후속 트리의 자식들과 일치시킨다. 예를 들어 위의 비효율적인 예제에 key를 추가하면 트리를 효율적으로 변환 할 수 있다.
<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>
이제 React는 '2014' key가있는 element가 새로운 element이고 '2015'및 '2016'key가있는 element가 방금 이동 한 것을 알고 있다.
실제로, key를 찾는 것은 어렵지 않다. 표시하려는 element에 이미 고유 한 ID가있을 수 있으므로 key는 데이터에서 온 것일 수 있다.
<li key={item.id}>{item.name}</li>
<li key={item.id}>{item.name}</li>
그렇지 않은 경우 모델에 새 ID property를 추가하거나 내용의 일부를 해시하여 key를 생성 할 수 있다. key는 그 형제들 사이에서만 유일하면 되며, 전체적으로는 유일하지 않아도 된다.
마지막 수단으로 배열의 항목 인덱스를 key로 전달할 수 있다. 항목 순서를 변경하지 않아도 재정렬이 느려지면 잘 작동한다. 이 것은 아이템들이 record 되는게 아니라면 잘 작동을 한다 하지만 recorder는 느리다.
index를 key로 사용하면 재정렬을 통해 component의 state에 문제가 발생할 수도 있습니다. component instance는 해당 key를 기반으로 업데이트되고 다시 사용됩니다. key가 index인 경우 항목을 이동하면 해당 항목이 변경됩니다. 결과적으로 통제되지 않은 입력과 같은 component state가 예상치 못한 방식으로 혼합되어 업데이트 될 수 있습니다.
Tradeoffs
reconciliation 알고리즘은 구현 세부 사항이라는 것을 기억하는 것이 중요하다. React가 모든 작업에서 전체 앱을 다시 렌더링 할 수 있다. 최종 결과는 동일하다. 이 컨텍스트에서 다시 렌더링한다는 것은 모든 component에 대해 렌더링을 호출한다는 의미이므로 React가 해당 component를 마운트 해제하고 다시 마운트한다는 의미는 아닙니다. 이전 섹션에서 설명한 규칙에 따라 차이만 적용됩니다.
일반적인 케이스를 더 빠르게 만들기 위해 휴리스틱을 정기적으로 수정하고 있다. 현재 구현에서는 하위 트리가 형제 사이에서 이동되었다는 사실을 표현할 수 있지만 다른 곳으로 이동했다고 말할 수는 없다. 알고리즘은 해당 전체 하위 트리를 다시 렌더링한다.
React는 heuristics에 의존하기 때문에, 그 뒤에있는 가정이 충족되지 않으면 성능이 저하된다.
알고리즘은 다른 components 유형의 하위 트리를 일치 시키려고 하지 않는다. 매우 유사한 출력을 가진 두 가지 component 유형을 번갈아 사용하는 경우 동일한 유형으로 만들 수 있습니다. 실무에서는 이러한 문제를 발견하지 못했다.
key는 안정적이고 예측 가능하며 고유해야 한다. Math.random ()에 의해 생성 된 key와 같은 불안정한 key는 많은 component instance와 DOM 노드가 불필요하게 다시 작성되어 하위 component의 성능 저하 및 state 손실을 초래할 수 있다.
참조: 리액트 16.3 공식 문서 영문판
댓글
댓글 쓰기