Virtual DOM Diff 알고리즘 분석-React

머리말

가상 DOM Diff 알고리즘을 이해해야 하는 이유는 무엇입니까?

작동 메커니즘을 이해하면 React 구성 요소의 수명 주기를 더 잘 이해하는 데 도움이 될 뿐만 아니라 React 프로그램을 더욱 최적화하는 데 도움이 됩니다.


1. 가상 DOM

가상 DOM: 가상 DOM, 일반 js 객체를 사용하여 DOM 구조를 설명합니다. 실제 DOM이 아니기 때문에 가상 DOM이라고 합니다.

Javascript 객체에서 가상 DOM은 Object 객체로 표시됩니다. 그리고 적어도 세 가지 속성을 포함합니다: 태그 이름(tag), 속성(attrs) 및 하위 요소 객체(children) 프레임워크마다 이 세 가지 속성에 대해 다른 이름을 가질 수 있습니다.

1. virtual DOM으로 해결해야 할 문제

1. react, vue 및 기타 기술이 출현하기 전에:
페이지에 표시되는 콘텐츠를 변경하려면 dom 트리를 통과하여 수정해야 하는 dom을 찾은 다음 스타일 동작 또는 구조를 수정하여 UI 업데이트 목적

꽤 많은 컴퓨팅 리소스를 소비합니다(거의 모든 dom 쿼리는 전체 dom 트리를 통과해야 함).

2. 가상 DOM(js 개체) 사용: dom의 모든 변경은 js 개체의 속성 변경이 되므로 js 개체의 속성 변경을 찾을 수 있으므로 dom 트리를 쿼리하는 것보다 성능 오버헤드가 적습니다.
Virtual DOM은 브라우저 성능 문제를 해결하도록 설계되었습니다.

2. 프런트엔드 - html을 받아 페이지에 표시하기까지의 과정

  1. DOM 트리 생성(HTML 요소 분석 및 DOM 트리 구축)
  2. StyleRules 만들기(요소의 CSS 파일 및 인라인 스타일을 분석하여 페이지에 대한 스타일 시트 생성)
  3. 렌더 트리 만들기(DOM 트리를 스타일 시트와 연결)
  4. 레이아웃 레이아웃(브라우저가 레이아웃을 시작하고 디스플레이 화면에 표시할 렌더 트리의 각 노드에 대한 정확한 좌표를 결정함)
    5. 그림 그리기(각 노드의 페인트 메서드를 호출하여 그립니다.)

3. DOM이란 무엇인가

문서 개체 모델(Document Object Model)은 HTML 및 XML용으로 제공되는 API입니다.

DOM 표준에 따르면 HTML과 XML은 모두 태그를 노드로 구성한 트리 구조로 DOM은 HTML과 XML의 구조적 본질을 동일하게 추상화한 후 Javascript 문서와 같은 스크립팅 언어를 통해 DOM에서 모델 표준에 따라 접근 및 동작한다. 콘텐츠.

Javascript는 DOM을 통해 웹 페이지 문서의 내용에 직접 액세스하고 조작할 수 있으며 js와 html 구조를 연결하는 인터페이스가 DOM입니다.

4. 돔 운영의 성능 오버헤드가 높은 이유:

dom 트리 쿼리의 성능 오버헤드가 높은 것은 아닙니다.

이유:

  1. dom 트리의 구현 모듈은 js 모듈 과 별개 이며 이러한 모듈 간 통신은 비용을 증가시킵니다.
  2. dom 작업으로 인한 브라우저의 재배치 및 다시 그리기는 성능 오버헤드를 크게 만듭니다.

비고 : PC 쪽에서는 성능 문제는 없으나(PC의 컴퓨팅 파워가 강하기 때문에) 모바일 단말의 발달로 스마트폰에서 실행되는 웹페이지가 많아지고, 휴대폰의 성능이 고르지 못하여, 그리고 성능 문제가 있을 것입니다.

5. 비교를 위해 virtual dom을 사용하는 이유

예를 들어:

한 작업에서 10개의 DOM 노드를 업데이트 해야 합니다 .브라우저는 첫 번째 DOM 요청을 받은 후 아직 9개의 업데이트 작업이 있다는 것을 모르기 때문에 프로세스를 즉시 실행하고 최종적으로 10번 실행 합니다 . 첫 번째 계산 후 다음 DOM 업데이트 요청 직후 이 노드의 좌표 값이 변경되며 이전 계산은 쓸모가 없습니다. DOM 노드 등의 좌표 값을 계산하는 것은 모두 성능 낭비입니다.

가상 DOM은 DOM을 즉시 작동시키지 않고 이 10가지 업데이트의 diff 내용을 로컬 JS 개체에 저장하고 최종적으로 JS 개체를 DOM 트리에 한 번에 첨부한 다음 후속 작업을 수행하여 불필요한 많은 작업을 방지합니다. 계산량 .

6. 가상 돔의 의미

이점:

  • vdom의 진정한 의미는 교차 플랫폼, 서버 측 렌더링을 달성하는 것입니다(따라서 반응 네이티브의 탄생 등).
  • 상당히 좋은 Dom 업데이트 전략을 제공합니다.

결점:

  • 극단적인 최적화를 수행할 수 없음: 가상 DOM + 합리적인 최적화가 대부분의 응용 프로그램의 성능 요구 사항을 충족시키기에 충분하지만 매우 높은 성능 요구 사항이 있는 일부 응용 프로그램에서는 가상 DOM이 극단적인 최적화 대상이 될 수 없습니다.
  • 많은 양의 DOM을 처음으로 렌더링할 때 가상 DOM의 추가 계층 계산으로 인해 innerHTML 삽입보다 느립니다.

7. virtual DOM 구현 과정

구성 요소가 렌더링될 때 render 함수가 호출됩니다.

  1. 렌더링 기능은 먼저 실제 DOM을 기반으로 가상 DOM을 생성합니다.
  2. 가상 DOM에 있는 노드의 데이터가 변경되면 새로운 Vnode가 생성됩니다.
  3. Vnode와 oldVnode를 비교하여 차이점이 있으면 실제 DOM에서 직접 수정한 다음 oldVnode Vnode의 값을 만듭니다.
  4. 실제 DOM이 페이지에 마운트됩니다.

2. 디프 알고리즘

1. DOM Diff 알고리즘이란?

웹 인터페이스는 DOM 트리로 구성되며, 그 중 일부가 변경되면 해당 DOM 노드가 변경되었음을 의미합니다. React에서 UI 인터페이스를 구축한다는 아이디어는 현재 상태로 인터페이스를 결정하는 것입니다. 이전과 이후의 두 상태는 두 인터페이스 집합에 해당하며 React는 두 인터페이스 간의 차이를 비교하므로 DOM 트리에서 Diff 알고리즘 분석이 필요합니다.

즉, 임의의 두 트리가 주어지면 가장 적은 변환 단계를 찾습니다. 그러나 표준 Diff 알고리즘의 복잡도는 O(n^3)이어야 하며 이는 분명히 성능 요구 사항을 충족할 수 없습니다. 인터페이스를 매번 새로 고침한다는 목적을 달성하기 위해서는 알고리즘을 최적화해야 합니다. 이것은 매우 어려워 보이지만 Facebook 엔지니어는 웹 인터페이스의 특성을 결합하고 두 가지 간단한 가정을 만들어 Diff 알고리즘의 복잡성을 O(n)으로 직접 줄였습니다.

  • 두 개의 동일한 구성 요소는 유사한 DOM 구조를 생성하고 서로 다른 구성 요소는 서로 다른 DOM 구조를 생성합니다.
  • 동일한 수준의 자식 노드 그룹의 경우 고유 ID로 구분할 수 있습니다.
    알고리즘 최적화는 React의 전체 인터페이스 Render의 기초이며, 이 두 가지 가정이 합리적이고 정확하다는 사실이 입증되어 전체 인터페이스 구성의 성능을 보장합니다.

2. 다른 노드 유형 비교

트리 간 비교를 하기 위해서는 먼저 두 개의 노드를 비교할 수 있어야 하는데, React에서는 두 개의 가상 DOM 노드를 비교하는데, 두 노드가 다를 때 어떻게 해야 할까요? 이렇게 두 가지 경우로 나뉩니다.

(1) 노드 유형이 다릅니다.

(2) 노드 유형은 동일하지만 속성이 다릅니다.

이 섹션에서는 첫 번째 경우를 먼저 살펴봅니다.

트리에서 같은 위치 전후에 서로 다른 유형의 노드가 출력되면 React는 이전 노드를 직접 삭제한 다음 새 노드를 생성하여 삽입합니다. 트리의 동일한 위치에서 서로 다른 유형의 노드를 두 번 출력한다고 가정합니다.

renderA: <div />
renderB: <span />
=> [removeNode <div />], [insertNode <span />]

노드가 div에서 span으로 변경되면 div 노드를 직접 삭제하고 새 span 노드를 삽입하면 됩니다. 이것은 실제 DOM 조작에 대한 우리의 이해와 일치합니다.

노드를 삭제한다는 것은 그 이후의 비교에서 삭제된 노드와 동일한 노드가 또 있는지를 확인하는 것이 아니라 해당 노드를 완전히 소멸시키는 것을 의미한다는 점에 유의해야 한다. 삭제된 노드 아래에 자식 노드가 있는 경우 이 자식 노드도 완전히 삭제되며 후속 비교에 사용되지 않습니다. 이것이 알고리즘의 복잡도를 O(n)으로 줄일 수 있는 이유입니다.

React가 같은 위치에서 다른 구성 요소를 만나면 첫 번째 구성 요소를 제거하고 새로 만든 구성 요소를 추가합니다. 이것은 첫 번째 가정의 적용입니다.각각의 구성 요소는 일반적으로 다른 DOM 구조를 생성합니다.기본적으로 동일하지 않은 DOM 구조를 비교하는 데 시간을 낭비하는 대신 새 구성 요소를 만들어 추가하는 것이 좋습니다.

다양한 유형의 노드에 대한 이 React의 처리 논리에서 우리는 React의 DOM Diff 알고리즘이 실제로 아래에 설명된 대로 트리 레이어를 레이어별로만 비교한다는 것을 쉽게 추론할 수 있습니다.

3. 레이어별 노드 비교

트리에 관해서는 대부분의 학생들이 이진 트리, 순회 및 최단 경로와 같은 복잡한 데이터 구조 알고리즘을 즉시 생각한다고 생각합니다. React에서 트리 알고리즘은 실제로 매우 간단합니다. 즉, 두 트리는 동일한 수준의 노드만 비교합니다. 아래 그림과 같이:
여기에 이미지 설명 삽입

React는 동일한 색상 상자 내의 DOM 노드, 즉 동일한 상위 노드 아래의 모든 하위 노드만 비교합니다. 노드가 더 이상 존재하지 않는 것으로 확인되면 해당 노드와 해당 자식 노드는 완전히 삭제되며 추가 비교에 사용되지 않습니다. 이러한 방식으로 전체 DOM 트리의 비교를 완료하려면 트리를 한 번만 탐색하면 됩니다.

예를 들어 다음 DOM 구조 변환을 고려하십시오.
여기에 이미지 설명 삽입

A 노드는 D 노드로 완전히 이동하므로 직관적으로 DOM Diff 연산은

A.parent.remove(A); 
D.append(A);

하지만 React는 단순히 같은 레이어 노드의 위치 변환만을 고려하기 때문에 **다른 레이어의 노드에 대해서는 단순 생성 및 삭제만 합니다. **루트 노드는 자식 노드에서 A가 누락된 것을 발견하면 A를 직접 파괴하고 D가 추가 자식 노드 A를 발견하면 새로운 A를 자식 노드로 생성합니다. 따라서 이 구조의 변환을 위한 실제 작업은 다음과 같습니다.

A.destroy();
A = new A();
A.append(new B());
A.append(new C());
D.append(A);

A를 루트 노드로 하는 트리가 완전히 재생성된 것을 볼 수 있습니다.

이러한 알고리즘은 약간 "단순"한 것처럼 보이지만 첫 번째 가정을 기반으로 합니다. 두 개의 서로 다른 구성 요소는 일반적으로 서로 다른 DOM 구조를 생성합니다. 공식 React 블로그에 따르면 이 가정은 지금까지 심각한 성능 문제를 일으키지 않았습니다. 이것은 물론 안정적인 DOM 구조를 유지하는 것이 자체 구성 요소를 구현할 때 성능을 향상시키는 데 도움이 된다는 힌트를 제공합니다. 예를 들어 실제로 DOM 노드를 제거하거나 추가하지 않고도 CSS를 통해 특정 노드를 숨기거나 표시할 수 있습니다.

4. DOM Diff 알고리즘에 의한 컴포넌트의 생명주기 이해

이전 기사에서 React 구성 요소의 수명 주기를 소개했는데 각 구성 요소는 실제로 DOM Diff 알고리즘과 밀접하게 관련되어 있습니다. 예를 들어 다음과 같은 방법이 있습니다.

  • 생성자: 구성 요소가 생성될 때 실행되는 생성자.
  • componentDidMount: 컴포넌트가 DOM 트리에 추가될 때 실행됩니다.
  • componentWillUnmount: 컴포넌트가 DOM 트리에서 제거될 때 실행되며, React에서는 컴포넌트가 파괴된 것으로 간주할 수 있습니다.
  • componentDidUpdate: 구성 요소가 업데이트될 때 실행됩니다.
    수명 주기와 DOM Diff 알고리즘 간의 관계. DOM 트리가 "shape1"에서 "shape2"로 전환될 때. 다음 메서드의 구현을 살펴보겠습니다.
    여기에 이미지 설명 삽입

브라우저 개발 도구 콘솔은 다음 결과를 출력합니다.

C will unmount.  C将被销毁
C is created.  C被创建
B is updated.  B被更新
A is updated.  A被更新
C did mount.  C被挂载
D is updated.  D被更新
R is updated.  R被更新

참고: 파괴된 구성 요소의 상위 및 하위 레이어와 동일한 레이어의 변경.

C 노드를 "이동"하는 대신 D 노드 아래에 완전히 다시 빌드하고 추가한 것을 볼 수 있습니다.

7. 동일한 유형의 노드 비교

두 번째 유형의 노드 비교는 동일한 유형의 노드이며 알고리즘은 비교적 간단하고 이해하기 쉽습니다. React는 속성을 재설정하여 노드 변환을 실현합니다. 예를 들어:

renderA: <div id="before" />
renderB: <div id="after" />
=> [replaceAttribute id "after"]

가상 DOM의 스타일 속성은 약간 다릅니다. 그 값은 단순한 문자열이 아니라 객체여야 하므로 변환 프로세스는 다음과 같습니다.

renderA: <div style={
    
    {
    
    color: 'red'}} />
renderB: <div style={
    
    {
    
    fontWeight: 'bold'}} />
=> [removeStyle color], [addStyle font-weight 'bold']

8. 리스트 노드 비교

위는 동일한 레이어에 있지 않은 노드의 비교를 설명한 것입니다. 정확히 동일하더라도 파괴되고 재생성됩니다. 그렇다면 동일한 레이어에 있을 때 어떻게 처리됩니까? 여기에는 목록 노드의 Diff 알고리즘이 포함됩니다. 저는 React를 사용하는 많은 학생들이 다음과 같은 경고를 접했다고 생각합니다.
여기에 이미지 설명 삽입

React가 목록을 만났지만 키를 찾을 수 없을 때 표시되는 경고입니다. 대부분의 인터페이스는 이 경고를 무시해도 올바르게 작동하지만 일반적으로 잠재적인 성능 문제를 나타냅니다. React는 이 목록을 효율적으로 업데이트하지 못할 수도 있다고 생각하기 때문입니다.

목록 노드에 대한 작업에는 일반적으로 추가, 제거 및 정렬이 포함됩니다. 예를 들어, 아래 그림에서 노드 F를 B와 C에 직접 삽입해야 합니다. jQuery에서는 $(B).after(F)를 직접 사용하여 달성할 수 있습니다. React에서는 새 인터페이스가 ABFCDE여야 한다고 React에 알리고 업데이트 인터페이스는 Diff 알고리즘에 의해 완료됩니다.
여기에 이미지 설명 삽입

이때 각 노드에 고유 식별자가 없으면 React가 각 노드를 식별할 수 없으므로 업데이트 프로세스가 매우 비효율적입니다. 즉, C를 F로, D를 C로, E를 D로 업데이트하고 마지막으로 E를 삽입합니다. 마디. 결과는 아래와 같습니다.
여기에 이미지 설명 삽입

React가 노드를 하나씩 업데이트하고 대상 노드로 전환하는 것을 볼 수 있습니다. 그리고 마지막으로 새 노드 E를 삽입하려면 많은 DOM 작업이 필요합니다. 그리고 각 노드에 고유 식별자(키)가 부여되면 React는 다음 그림과 같이 새 노드를 삽입할 올바른 위치를 찾을 수 있습니다.
여기에 이미지 설명 삽입

리스트 노드의 순서를 조정하는 것은 실제로 삽입이나 삭제와 유사하므로 변환 과정을 예를 들어 살펴보자. 나무 모양을 shape5에서 shape6으로 변환합니다.
여기에 이미지 설명 삽입

즉, 같은 레이어에 있는 노드의 위치를 ​​조정하는 것입니다. 키가 제공되지 않으면 React는 B와 C 이후 해당 위치의 구성 요소 유형이 다른 것으로 간주하여 완전히 삭제하고 다시 빌드합니다.콘솔 출력은 다음과 같습니다.

B will unmount.
C will unmount.
C is created.
B is created.
C did mount.
B did mount.
A is updated.
R is updated.

그리고 다음 코드와 같이 키가 제공된 경우:

shape5: function() {
    
    
  return (
    <Root>
      <A>
        <B key="B" />
        <C key="C" />
      </A>
    </Root>
  );
},
shape6: function() {
    
    
  return (
    <Root>
      <A>
        <C key="C" />
        <B key="B" />
      </A>
    </Root>
  );
},

그러면 콘솔 출력은 다음과 같습니다.

C is updated.
B is updated.
A is updated.
R is updated.

목록 노드에 고유한 키 속성을 제공하면 React가 비교를 위해 올바른 노드를 찾는 데 도움이 될 수 있으므로 DOM 작업 수를 크게 줄이고 성능을 향상시킬 수 있습니다.

추천

출처blog.csdn.net/qq_43000315/article/details/125467897