가상 DOM의 본질을 이해하고 익히는 방법은 무엇입니까? 나는 모든 사람에게 Snabbdom 프로젝트를 배울 것을 권장합니다.
Snabbdom은 가상 DOM 구현 라이브러리입니다. 첫째, 코드가 상대적으로 작고 핵심 코드가 몇 백 줄에 불과합니다. 둘째, Vue는 이 프로젝트의 아이디어를 활용하여 가상 DOM을 구현합니다. 이 프로젝트의 디자인/구현 및 확장 아이디어는 참고할 가치가 있습니다.
snabb /snab/, 스웨덴어는 빠르다는 뜻입니다.
편안하게 앉은 자세를 조정하고 힘내세요. 가상 DOM을 배우기 위해서는 먼저 DOM에 대한 기본 지식과 JS로 DOM을 직접 운영할 때의 문제점을 알아야 합니다.
DOM(문서 개체 모델)은 개체 트리 구조를 사용하여 HTML/XML 문서를 나타내는 문서 개체 모델입니다. 노드는 개체를 포함합니다. DOM API의 메소드를 사용하면 특정 방식으로 이 트리를 조작할 수 있으며, 문서의 구조, 스타일 또는 컨텐츠를 변경할 수 있습니다.
DOM 트리의 모든 노드는 첫 번째 Node
Node
기본 클래스입니다. Element
, Text
및 Comment
모두 여기에서 상속됩니다.
즉, Element
, Text
및 Comment
각각 ELEMENT_NODE
라고 불리는 세 개의 특수 Node
입니다.
TEXT_NODE
및 COMMENT_NODE
요소 노드(HTML 태그), 텍스트 노드 및 주석 노드를 나타냅니다. Element
HTMLElement
라는 하위 클래스도 있습니다. HTMLElement
와 Element
의 차이점은 무엇입니까? HTMLElement
<span>
, <img>
등과 같은 HTML의 요소를 나타내며 <svg>
와 같은 일부 요소는 HTML 표준이 아닙니다. 다음 방법을 사용하여 이 요소가 HTMLElement
인지 확인할 수 있습니다.
document.getElementById('myIMG') instanceof HTMLElement;
브라우저가 DOM을 생성하는 것은 "비용이 많이 듭니다". 전형적인 예를 들어보겠습니다. document.createElement('p')
를 통해 간단한 p 요소를 만들고 모든 속성을 인쇄할 수 있습니다.
복잡한 DOM 트리를 자주 업데이트하면 성능 문제가 발생하게 됩니다. 가상 DOM은 기본 JS 개체를 사용하여 DOM 노드를 설명하므로 JS 개체를 만드는 것이 DOM 개체를 만드는 것보다 훨씬 저렴합니다.
VNode는 Snabbdom의 가상 DOM을 설명하는 객체 구조입니다.
type Key = string number | 인터페이스 VNode { // CSS 선택기(예: 'p#container'). sel: 문자열 | 정의되지 않음; // 모듈을 통해 CSS 클래스, 속성 등을 조작합니다. 데이터: VNodeData 정의되지 않음; // 가상 자식 노드 배열, 배열 요소는 문자열일 수도 있습니다. 어린이: 배열<VNode 문자열> | // 생성된 실제 DOM 객체를 가리킵니다. 느릅나무: 노드 | 정의되지 않음; /** * 텍스트 속성에는 두 가지 상황이 있습니다. * 1. sel 선택기가 설정되지 않아 노드 자체가 텍스트 노드임을 나타냅니다. * 2. sel이 설정되어 이 노드의 내용이 텍스트 노드임을 나타냅니다. */ 텍스트: 문자열 | 정의되지 않음; // 불필요한 재구성 작업을 효과적으로 피하기 위해 형제 요소 간에 고유해야 하는 기존 DOM에 대한 식별자를 제공하는 데 사용됩니다. 키: 키가 정의되지 않음; } // vnode.data, 클래스 또는 수명 주기 함수 후크 등에 대한 일부 설정 인터페이스 VNodeData { 소품?: 소품; 속성?: 속성; 수업?: 수업; 스타일?: VNodeStyle; 데이터세트?: 데이터세트; 에?: 에; 부착데이터?: 부착데이터; 후크?: 후크; 키?: 키; ns?: 문자열; // SVG의 경우 fn?: () => VNode; // 썽크용 args?: any[]; // 썽크용 is?: 문자열; // 맞춤 요소 v1의 경우 [key: string]: any; // 기타 타사 모듈의 경우 }
예를 들어, 다음과 같이 vnode 객체를 정의합니다:
const vnode = h( 'p#컨테이너', { 클래스: { 활성: 참 } }, [ h('span', { style: {fontWeight: 'bold' } }, '이것은 굵게 표시됩니다.'), '그리고 이것은 단지 일반적인 텍스트입니다' ]);
h(sel, b, c)
함수를 통해 vnode 객체를 생성합니다. h()
코드 구현에서는 주로 b 및 c 매개변수가 존재하는지 확인하고 이를 데이터와 하위 항목으로 처리합니다. 마지막으로 위에서 정의한 VNode
유형 형식이 vnode()
함수를 통해 반환됩니다.
먼저 실행 프로세스에 대한 간단한 예제 다이어그램을 취하고 먼저 일반적인 프로세스 개념을 살펴보겠습니다.
Diff 처리는 새 노드와 이전 노드 간의 차이를 계산하는 데 사용되는 프로세스입니다.
Snabbdom이 실행하는 샘플 코드를 살펴보겠습니다.
import { 초기화, 클래스모듈, 소품모듈, 스타일모듈, 이벤트리스너모듈, 시간, } 'snabbdom'에서; const 패치 = 초기화([ // 모듈을 전달하여 패치 함수 classModule을 초기화합니다. // 클래스 함수 propsModule을 활성화합니다. // props 전달 지원 styleModule, // 인라인 스타일 및 애니메이션 지원 eventListenersModule, // 이벤트 수신 추가]); // <p id="컨테이너"></p> const 컨테이너 = document.getElementById('컨테이너'); const vnode = h( 'p#container.two.classes', { 켜짐: { 클릭: someFn } }, [ h('span', { style: {fontWeight: 'bold' } }, '이것은 굵게 표시됩니다.'), '그리고 이것은 단지 일반적인 텍스트입니다', h('a', { props: { href: '/foo' } }, "내가 데려다줄게!"), ] ); // 빈 요소 노드를 전달합니다. 패치(컨테이너, vnode); const newVnode = h( 'p#container.two.classes', { 켜짐: { 클릭: anotherEventHandler } }, [ 시간( '기간', { 스타일: { FontWeight: 'normal', 글꼴 스타일: 'italic' } }, '이제 이탤릭체가 되었습니다.' ), '그리고 이것은 여전히 일반적인 텍스트입니다', h('a', { props: { href: ''/bar' } }, "내가 데려다줄게!"), ] ); // patch()를 다시 호출하여 이전 노드를 새 노드로 업데이트합니다. patch(vnode, newVnode);
프로세스 다이어그램과 샘플 코드에서 볼 수 있듯이 Snabbdom의 실행 프로세스는 다음과 같이 설명됩니다.
먼저 초기화를 위해 init()
호출하고 초기화 중에 사용할 모듈을 구성해야 합니다. 예를 들어, classModule
모듈은 객체 형태로 요소의 클래스 속성을 구성하는 데 사용됩니다. eventListenersModule
모듈은 이벤트 리스너 등을 구성하는 데 사용됩니다. patch()
함수는 init()
호출된 후에 반환됩니다.
h()
함수를 통해 초기화된 vnode 객체를 생성하고, patch()
함수를 호출하여 업데이트한 후, 마지막으로 createElm()
통해 실제 DOM 객체를 생성합니다.
업데이트가 필요한 경우 새 vnode 객체를 만들고, patch()
함수를 호출하여 업데이트하고, patchVnode()
및 updateChildren()
통해 이 노드와 하위 노드의 차등 업데이트를 완료합니다.
Snabbdom은 핵심 코드에 모든 것을 작성하는 대신 모듈 설계를 사용하여 관련 속성의 업데이트를 확장합니다. 그렇다면 이것은 어떻게 설계되고 구현됩니까? 다음으로 강강 디자인의 핵심 콘텐츠인 Hooks, 즉 생명주기 기능을 먼저 살펴보겠습니다.
Snabbdom은 후크 기능이라고도 하는 일련의 풍부한 수명 주기 기능을 제공합니다. 이러한 수명 주기 기능은 모듈에 적용할 수 있거나 vnode에서 직접 정의할 수 있습니다. 예를 들어 다음과 같이 vnode에서 후크 실행을 정의할 수 있습니다
. 키: 'myRow', 후크: { 삽입: (vnode) => { console.log(vnode.elm.offsetHeight); }, }, });
모든 라이프사이클 함수는 다음과 같이 선언됩니다.
이름 | 트리거 노드 | 콜백 매개변수 | |
---|---|---|---|
pre | 패치 시작 실행 | 없음 | |
init | vnode가 추가됩니다 | vnode | |
vnode를 기반으로 DOM 요소가 | create | 됩니다 emptyVnode, vnode | |
insert | 요소가 DOM에 삽입됩니다. | vnode | |
prepatch | 요소는 | oldVnode를패치하려고 함 | oldVnode, vnode |
update | 요소가 업데이트됨 | oldVnode, vnode | |
postpatch | 요소가 패치됨 | oldVnode, vnode | |
destroy | 요소가 직접 또는 간접적으로 제거됨 | vnode | |
remove | 요소가 DOM에서 vnode를 제거함 | vnode, removeCallback | |
post | 이 패치 프로세스를 완료 | 함 |
적용되지 않음 모듈에: pre
, create
, update
, destroy
, remove
, post
. vnode 선언에 적용할 수 있는 항목은 init
, create
, insert
, prepatch
, update
, postpatch
, destroy
, remove
입니다.
Kangkang이 어떻게 구현되는지 살펴보겠습니다. 예를 들어, Kangkang의 설명인 classModule
모듈을 살펴보겠습니다.
import { VNode, VNodeData } from "../vnode"; "./module"에서 { 모듈 }을 가져옵니다. 내보내기 유형 Classes = Record<string, boolean>; 함수 updateClass(oldVnode: VNode, vnode: VNode): void { // 클래스 속성 업데이트에 대한 자세한 내용은 다음과 같습니다. 지금은 무시하세요. // ... } import const classModule: Module = { create: updateClass, update: updateClass };
객체의 키는 Module
객체의 이름입니다. 다음과 같이:
수입 { 프리훅, CreateHook, 업데이트후크, DestroyHook, 제거후크, 포스트훅, } "../후크"에서; 내보내기 유형 모듈 = 부분<{ pre: PreHook; 생성: CreateHook; 업데이트: UpdateHook; 파괴하다: DestroyHook; 제거: RemoveHook; 게시물: PostHook; }>;
TS의 Partial
객체의 각 키 속성이 비어 있을 수 있음을 의미합니다. 즉, 모듈 정의에서 관심 있는 후크를 정의하면 됩니다. 이제 후크가 정의되었으므로 프로세스에서 어떻게 실행됩니까? 다음으로 init()
함수를 살펴보겠습니다:
// 모듈에 정의될 수 있는 후크는 무엇입니까? const 후크: Array<keyof Module> = [ "만들다", "업데이트", "제거하다", "파괴하다", "사전", "우편", ]; 내보내기 함수 초기화( 모듈: 배열<부분<모듈>>, domApi?: DOMAPI, 옵션?: 옵션 ) { //모듈에 정의된 후크 함수가 최종적으로 여기에 저장됩니다. const cbs: ModuleHooks = { 만들다: [], 업데이트: [], 제거하다: [], 파괴하다: [], 사전: [], 우편: [], }; // ... // 모듈에 정의된 후크를 순회하여 함께 저장합니다. for (후크의 const 후크) { for (모듈의 const 모듈) { const currentHook = 모듈[후크]; if (currentHook !== 정의되지 않음) { (cbs[hook] as any[]).push(currentHook); } } } // ... }
init()
실행 중에 먼저 각 모듈을 순회한 다음 cbs
객체에 후크 함수를 저장하는 것을 볼 수 있습니다. 실행할 때 patch()
함수를 사용할 수 있습니다:
내보내기 함수 init( 모듈: 배열<부분<모듈>>, domApi?: DOMAPI, 옵션?: 옵션 ) { // ... 반환 함수 패치( oldVnode: VNode | vnode: VNode ): VNode { // ... // 패치가 시작되고, pre Hook을 실행합니다. for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); // ... } }
여기서는 pre
Hook을 예로 들어보겠습니다. pre
Hook의 실행 시간은 패치가 실행되기 시작하는 시점입니다. patch()
함수는 실행 시작 시 cbs
에 저장된 pre
관련 Hook을 주기적으로 호출하는 것을 볼 수 있습니다. 다른 라이프사이클 함수에 대한 호출은 이와 유사하며 소스 코드의 다른 곳에서 해당 라이프사이클 함수 호출을 볼 수 있습니다.
여기서 디자인 아이디어는 관찰자 패턴 입니다. Snabbdom은 비핵심 기능을 모듈에 배포하여 구현합니다. 라이프 사이클 정의와 결합하여 모듈은 관심 있는 후크를 정의할 수 있습니다. 그런 다음 init()
가 실행되면 이러한 후크를 등록하기 위해 cbs
객체로 처리됩니다. 실행 시간이 오면 호출하십시오. 이 후크는 모듈 처리를 알리는 데 사용됩니다. 이로써 핵심 코드와 모듈 코드가 구분됩니다. 여기에서 관찰자 패턴이 코드 분리의 일반적인 패턴임을 알 수 있습니다.
다음으로 Kangkang 핵심 함수인 patch()
를 살펴보겠습니다. 이 함수는 init()
호출 후에 반환됩니다. 해당 함수는 VNode를 마운트하고 업데이트하는 것입니다.
function patch(oldVnode: VNode | Element | DocumentFragment , vnode: VNode): VNode { // 단순화를 위해 DocumentFragment에 주의를 기울이지 마세요. // ... }
oldVnode
매개변수는 이전 VNode나 DOM 요소 또는 문서 조각이고, vnode
매개변수는 업데이트된 객체입니다.
모듈에 등록된 pre
후크를 호출하는
프로세스에 대한 설명을 직접 게시합니다
.oldVnode
Element
인 경우 빈 vnode
객체로 변환되고 elm
속성에 기록됩니다.
여기서 판단은 Element
(oldVnode as any).nodeType === 1
nodeType === 1
여기에 정의된 ELEMENT_NODE임을 나타냅니다.
그런 다음 oldVnode
와 vnode
동일한지 확인합니다. 여기에서 sameVnode()
호출되어 다음을 확인합니다.
function sameVnode(vnode1: VNode, vnode2: VNode): boolean //같은 키입니다. const isSameKey = vnode1.key === vnode2.key; // 웹 구성요소, 맞춤 요소 태그 이름, 여기를 참조하세요. // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement const isSameIs = vnode1.data?.is === vnode2.data?.is; //같은 선택자입니다. const isSameSel = vnode1.sel === vnode2.sel; // 3개 모두 동일합니다. return isSameSel && isSameKey && isSameIs; }
patchVnode()
호출하십시오.createElm()
호출하여 새 DOM 노드를 생성한 후 DOM 노드를 삽입하고 이전 DOM 노드를 삭제합니다.위 작업에 포함된 vnode 객체에 등록된 insert
Hook queue인 patchVnode()
createElm()
호출하여 새로운 노드를 삽입할 수 있습니다. 이것이 수행되는 이유는 createElm()
에서 언급됩니다.
마지막으로 모듈에 등록된 post
Hook이 호출됩니다.
프로세스는 기본적으로 vnode가 동일하면 diff를 수행하고, 다르면 새 vnode를 생성하고 이전 vnode를 삭제하는 것입니다. 다음으로 createElm()
이 DOM 노드를 생성하는 방법을 살펴보겠습니다.
createElm()
vnode 구성을 기반으로 DOM 노드를 생성합니다. 프로세스는 다음과 같습니다:
vnode 객체에 존재할 수 있는 init
후크를 호출합니다.
그런 다음 몇 가지 상황을 처리합니다.
vnode.sel === '!'
인 경우 Snabbdom이 원래 노드를 삭제하는 데 사용하는 방법이므로 새 주석 노드가 삽입됩니다. createElm()
후에 이전 노드가 삭제되므로 이 설정으로 제거 목적을 달성할 수 있습니다.
vnode.sel
선택기 정의가 존재하는 경우:
선택기를 구문 분석하고 id
, tag
및 class
가져옵니다.
document.createElement()
또는 document.createElementNS
호출하여 DOM 노드를 생성하고 이를 vnode.elm
에 기록하고 이전 단계의 결과에 따라 id
, tag
및 class
설정합니다.
모듈에서 create
후크를 호출합니다.
children
배열 처리:
children
배열인 경우 createElm()
재귀적으로 호출하여 하위 노드를 생성한 다음, appendChild
호출하여 vnode.elm
아래에 마운트합니다.
children
이 배열은 아니지만 vnode.text
존재한다는 것은 이 요소의 내용이 텍스트임을 의미합니다. 이때 createTextNode
호출되어 텍스트 노드를 생성하고 vnode.elm
아래에 마운트됩니다.
vnode에서 create
후크를 호출합니다. 그리고 vnode의 insert
후크를 insert
후크 대기열에 추가합니다.
남은 상황은 vnode.sel
존재하지 않아 노드 자체가 텍스트임을 나타내는 것입니다. 그런 다음 createTextNode
호출하여 텍스트 노드를 생성하고 이를 vnode.elm
에 기록합니다.
마지막으로 vnode.elm
반환합니다.
createElm()
sel
선택기의 다양한 설정을 기반으로 DOM 노드를 생성하는 방법을 선택하는 전체 프로세스에서 볼 수 있습니다. 여기에 추가할 세부 사항이 있습니다: patch()
에 언급된 insert
후크 대기열입니다. 이 insert
Hook Queue가 필요한 이유는 DOM이 실제로 삽입될 때까지 기다려야 실행이 가능하며, 모든 자손 노드가 삽입될 때까지 기다려야 하므로 DOM의 크기와 위치 정보를 계산할 수 있기 때문입니다. insert
요소가 정확해야 합니다. 위의 자식 노드를 생성하는 프로세스와 결합된 createElm()
자식 노드를 생성하는 재귀 호출이므로 대기열은 먼저 자식 노드를 기록한 다음 대기열 자체를 기록합니다. 이렇게 하면 patch()
끝에서 큐를 실행할 때 순서가 보장될 수 있습니다.
다음으로 Snabbdom이 어떻게 patchVnode()
사용하여 가상 DOM의 핵심인 diff를 수행하는지 살펴보겠습니다. patchVnode()
의 처리 흐름은 다음과 같습니다.
먼저 vnode에서 prepatch
후크를 실행합니다.
oldVnode와 vnode가 동일한 객체 참조인 경우 처리 없이 직접 반환됩니다.
모듈 및 vnode에서 update
후크를 호출합니다.
vnode.text
정의되지 않은 경우 여러 가지 children
사례가 처리됩니다.
즉, oldVnode.children
과 vnode.children
모두 존재하고 동일하지 않은 경우입니다. 그런 다음 updateChildren
호출하여 업데이트합니다.
vnode.children
존재하지만 oldVnode.children
존재하지 않습니다. oldVnode.text
존재하는 경우 먼저 이를 지운 다음 addVnodes
호출하여 새 vnode.children
추가하세요.
vnode.children
존재하지 않지만 oldVnode.children
존재합니다. oldVnode.children
제거하려면 removeVnodes
호출하세요.
oldVnode.children
도 vnode.children
도 존재하지 않는 경우. oldVnode.text
가 있으면 지웁니다.
vnode.text
정의되고 oldVnode.text
와 다른 경우. oldVnode.children
있으면 removeVnodes
호출하여 삭제하세요. 그런 다음 textContent
통해 텍스트 내용을 설정합니다.
마지막으로 vnode에서 postpatch
후크를 실행합니다.
class
, style
등 diff에 있는 자체 노드의 관련 속성에 대한 변경 사항이 모듈에 의해 업데이트되는 것을 볼 수 있습니다. 그러나 여기서는 필요한 경우 너무 많이 확장하지 않습니다. 모듈 관련 코드를 살펴볼 수 있습니다. diff의 주요 핵심 처리는 children
에 중점을 둡니다. 다음으로 Kangkang diff는 children
의 여러 관련 기능을 처리합니다.
매우 간단합니다. 먼저 createElm()
호출하여 생성한 다음 해당 부모에 삽입합니다.
remove
destory
destory
, 이 후크가 먼저 호출됩니다. 논리는 먼저 vnode 객체에서 후크를 호출한 다음 모듈에서 후크를 호출하는 것입니다. 그런 다음 이 후크는 vnode.children
에서 이 순서대로 재귀적으로 호출됩니다.remove
이 후크는 현재 요소가 상위 요소에서 삭제될 때만 트리거됩니다. 제거된 요소의 하위 요소는 트리거되지 않으며 이 후크는 모듈과 vnode 객체 모두에서 호출됩니다. 먼저 모듈을 켜고 vnode를 호출하세요. 더 특별한 점은 모든 remove
호출될 때까지 요소가 실제로 제거되지 않는다는 것입니다. 이로 인해 일부 지연된 삭제 요구 사항이 충족될 수 있습니다.위에서 보면 두 후크의 호출 논리가 다르다는 것을 알 수 있습니다. 특히, remove
상위 요소와 직접 분리된 요소에서만 호출됩니다.
updateChildren()
자식 노드 diff를 처리하는 데 사용되며 Snabbdom에서는 비교적 복잡한 함수이기도 합니다. 일반적인 아이디어는 oldCh
및 newCh
에 대해 총 4개의 헤드 및 테일 포인터를 설정하는 것입니다. 이 4개의 포인터는 각각 oldStartIdx
, oldEndIdx
, newStartIdx
및 newEndIdx
입니다. 그런 다음 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)
루프에서 두 배열을 비교하여 재사용 및 업데이트를 위한 동일한 부분을 찾고, 각 비교에 대해 최대 한 쌍의 포인터로 이동합니다. 자세한 순회 프로세스는 다음 순서로 처리됩니다.
4개의 포인터 중 하나가 vnode == null을 가리키는 경우 포인터는 start++ 또는 end--와 같이 가운데로 이동합니다. null 발생에 대해서는 나중에 설명합니다.
이전 시작 노드와 새 시작 노드가 동일한 경우, 즉 sameVnode(oldStartVnode, newStartVnode)
true를 반환하는 경우 patchVnode()
사용하여 diff를 수행하면 두 시작 노드 모두 중앙을 향해 한 단계 이동합니다.
이전 끝 노드와 새 끝 노드가 동일한 경우 patchVnode()
도 사용되며 두 끝 노드는 한 단계 뒤로 중앙으로 이동합니다.
이전 시작 노드가 새 끝 노드와 동일한 경우 patchVnode()
사용하여 업데이트를 먼저 처리합니다. 그런 다음 oldStart에 해당하는 DOM 노드를 이동해야 합니다. 이동 전략은 oldEndVnode
에 해당하는 DOM 노드의 다음 형제 노드 앞으로 이동하는 것입니다. 왜 이렇게 움직이는 걸까요? 우선, oldStart는 newEnd와 동일합니다. 이는 현재 루프 처리에서 이전 배열의 시작 노드가 오른쪽으로 이동한다는 것을 의미합니다. 각 처리가 헤드 및 테일 포인터를 가운데로 이동하기 때문입니다. 이때 oldEnd가 아직 처리되지 않았을 수 있으나, 현재 new array 처리 중 oldStart가 마지막으로 판단되었으므로 다음 형제로 이동하는 것이 합리적입니다. oldEnd의 노드. 이동이 완료되면 oldStart++ 및 newEnd-- 해당 배열의 중앙으로 한 단계 이동합니다.
이전 끝 노드가 새 시작 노드와 동일한 경우 patchVnode()
사용하여 먼저 업데이트를 처리한 다음 oldEnd에 해당하는 DOM 노드를 oldStartVnode
에 해당하는 DOM 노드로 이동하는 이유는 다음과 같습니다. 이전 단계와 동일합니다. 이동이 완료된 후 oldEnd--, newStart++.
위의 경우 중 어느 것도 해당되지 않으면 newStartVnode의 키를 사용하여 oldChildren
에서 아래 첨자 idx를 찾습니다. 아래 첨자가 존재하는지 여부에 따라 두 가지 처리 논리가 있습니다.
아래 첨자가 존재하지 않으면 newStartVnode가 새로 생성된다는 의미입니다. createElm()
통해 새로운 DOM을 생성하고 이를 oldStartVnode
에 해당하는 DOM 앞에 삽입합니다.
첨자가 존재하는 경우 두 가지 경우로 처리됩니다.
두 vnode의 선택이 다르면 여전히 새로 생성된 것으로 간주하고 createElm()
통해 새 DOM을 생성하고 oldStartVnode
에 해당하는 DOM 앞에 삽입합니다. .
sel이 동일하면 patchVnode()
통해 업데이트가 처리되고, oldChildren
의 첨자에 해당하는 vnode가 정의되지 않은 것으로 설정됩니다. 이것이 이전 이중 포인터 순회에서 == null이 나타나는 이유입니다. 그런 다음 업데이트된 노드를 oldStartVnode
에 해당하는 DOM에 삽입합니다.
위 작업이 완료되면 newStart++.
순회가 완료된 후에도 처리해야 할 두 가지 상황이 남아 있습니다. 하나는 oldCh
가 완전히 처리되었지만 newCh
에 여전히 새 노드가 있고 나머지 newCh
마다 새 DOM을 생성해야 한다는 것입니다. 다른 하나는 newCh
완전히 처리되었으며 oldCh
에 여전히 이전 노드가 있다는 것입니다. 중복 노드를 제거해야 합니다. 두 가지 상황은 다음과 같이 처리됩니다
. parentElm: 노드, oldCh: VNode[], newCh: VNode[], 삽입된VnodeQueue: VNodeQueue ) { // 이중 포인터 순회 프로세스. // ... // newCh에는 생성해야 할 새 노드가 있습니다. if (newStartIdx <= newEndIdx) { //마지막 처리된 newEndIdx 이전에 삽입되어야 합니다. before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; 추가Vnode( 부모엘름, 전에, 새로운Ch, 새로운StartIdx, 새로운EndIdx, 삽입된VnodeQueue ); } // oldCh에는 제거해야 할 오래된 노드가 여전히 있습니다. if (oldStartIdx <= oldEndIdx) { RemoveVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
실제 예를 통해 updateChildren()
의 처리 과정을 살펴보겠습니다.
초기 상태는 다음과 같습니다. 이전 자식 노드 배열은 [A, B, C]이고 새 노드 배열은 [B, A, C]입니다. , 디]:
첫 번째 비교에서는 시작 노드와 끝 노드가 다르기 때문에 이전 노드에 newStartVnode가 있는지 확인하고 oldCh[1]의 위치를 찾은 다음 patchVnode()
실행하여 먼저 업데이트한 다음 oldCh[1을 설정합니다. ] = undefed 이고 oldStartVnode
앞에 DOM을 삽입하면 newStartIdx
한 단계 뒤로 이동하며 처리 후 상태는 다음과 같습니다.
두 번째 비교에서는 oldStartVnode
와 newStartVnode
동일하며, update를 위해 patchVnode()
실행하면 oldStartIdx
와 newStartIdx
처리 후 상태는 다음과 같습니다.
세 번째 비교에서는 oldStartVnode == null
, oldStartIdx
가운데로 이동하고 상태가 다음과 같이 업데이트됩니다.
4차 비교에서는 oldStartVnode
와 newStartVnode
동일하며, update를 위해 patchVnode()
실행하면 oldStartIdx
와 newStartIdx
처리 후 상태는 다음과 같다.
이때 oldStartIdx
oldEndIdx
보다 크며 루프가 종료됩니다. 이때 newCh
에는 아직 처리되지 않은 새로운 노드가 있는데, 이를 삽입하려면 addVnodes()
호출해야 합니다. 최종 상태는 다음과 같습니다.
, 여기에서 가상 DOM의 핵심 내용을 정리했습니다. Snabbdom의 설계 및 구현 원리는 매우 훌륭하다고 생각합니다. 시간이 있으시면 Kangkang 소스 코드의 세부 사항을 살펴보실 수 있습니다. 아이디어는 배울 가치가 있습니다.