mount, patch, diffing, reconciliation, update...
Vue3은 렌더링을 어떻게 수행할까요?
공식 문서의 번역입니다. https://vuejs.org/guide/extras/rendering-mechanism.html
Vue는 템플릿을 어떻게 가져와 실제 DOM 노드로 변환할까요?
Vue는 이러한 DOM 노드를 어떻게 효율적으로 업데이트할까요?
여기에서 Vue의 내부 렌더링 메커니즘을 살펴봄으로써 이러한 질문에 대한 설명을 시도합니다.
Virtual DOM
가상 DOM은 특정 기술보다 패턴에 가깝기 때문에 하나의 정식 구현이 없습니다.
간단한 예를 사용하여 아이디어를 설명할 수 있습니다.
const vnode = {
type: 'div',
props: {
id: 'hello'
},
children: [
/* more vnodes */
]
}
런타임 렌더러는 가상 DOM 트리를 탐색하고 이 트리를 기반으로 실제 DOM 트리를 구성할 수 있습니다.
이 프로세스를 마운트(mount)라고 합니다.
가상 DOM 트리의 복사본이 두 개 있는 경우 렌더러는 두 트리를 살펴보고 비교하여 차이점을 파악하고
이러한 변경 사항을 실제 DOM에 적용할 수도 있습니다.
이 프로세스를 "patch"라고 하며 "diffing" 또는 "reconciliation"이라고도 합니다.
주 : 만드는 방법을 설명하지 않음. 구조와 데이터로만 UI를 표현
Render Pipeline
- Compile : Vue 템플릿은 가상 DOM 트리를 반환하는 함수인 렌더 함수로 컴파일됩니다. 이 단계는 빌드 단계를 통해 미리 수행하거나 런타임 컴파일러를 사용하여 즉석에서 수행할 수 있습니다.
- Mount : 런타임 렌더러는 렌더 함수를 호출하고 반환된 가상 DOM 트리를 살펴보고 이를 기반으로 실제 DOM 노드를 생성합니다. 이 단계는 반응 효과(reactive effect)를 통해 수행되므로 사용된 모든 반응 종속성을 추적합니다.
- Patch : 마운트 중에 사용된 종속성이 변경되면 효과가 다시 실행됩니다. 이번에는 업데이트된 새로운 가상 DOM 트리가 생성됩니다. 런타임 렌더러는 새 트리를 탐색하고 이전 트리와 비교하고 필요한 업데이트를 실제 DOM에 적용합니다.
효과 > 리렌더링 > 렌더 함수 호출 > 가상돔 변경 > 마운트 / (패치/디핑/조화)
Templates vs. Render Functions
- 템플릿은 실제 HTML에 더 가깝습니다. 이를 통해 기존 HTML 스니펫을 재사용하고, 접근성 모범 사례를 적용하고, CSS로 스타일을 지정하고, 디자이너가 이해하고 수정할 수 있습니다.
- 템플릿은 보다 선언적인 구문으로 인해 정적으로 분석하기가 더 쉽습니다. 이를 통해 Vue의 템플릿 컴파일러는 가상 DOM의 성능을 향상시키기 위해 많은 컴파일 시간 최적화를 적용할 수 있습니다(아래에서 논의함).
Compiler-Informed Virtual DOM
컴파일 타임 정보를 사용하는 가상돔
정적 호이스팅
<div>
<div>foo</div> <!-- hoisted -->
<div>bar</div> <!-- hoisted -->
<div>{{ dynamic }}</div>
</div>
패치 플래그
동적 바인딩이 있는 단일 요소의 경우 컴파일 시간에 많은 정보를 추론할 수도 있습니다.<!-- class binding only -->
<div :class="{ active }"></div>
<!-- id and value bindings only -->
<input :id="id" :value="value">
<!-- text children only -->
<div>{{ dynamic }}</div>
Inspect in Template Explorer
이러한 요소에 대한 렌더 함수 코드를 생성할 때
Vue는 vnode 생성 호출에서 직접 각 요소에 필요한 업데이트 타입을 인코딩합니다.
createElementVNode("div", {
class: _normalizeClass({ active: _ctx.active })
}, null, 2 /* CLASS */)
if (vnode.patchFlag & PatchFlags.CLASS /* 2 */) {
// update the element's class
}
비트 단위 검사는 매우 빠릅니다.
패치 플래그를 사용하면 Vue는 동적 바인딩으로 요소를 업데이트할 때 필요한 최소한의 작업을 수행할 수 있습니다.
export function render() {
return (_openBlock(), _createElementBlock(_Fragment, null, [
/* children */
], 64 /* STABLE_FRAGMENT */))
}
트리 평탄화(Tree Flattening)
이전 예제에서 생성된 코드를 다시 살펴보면
반환된 가상 DOM 트리의 루트가 특별한 createElementBlock() 호출을 사용하여 생성되었음을 알 수 있습니다.
export function render() {
return (_openBlock(), _createElementBlock(_Fragment, null, [
/* children */
], 64 /* STABLE_FRAGMENT */))
}
개념적으로 "블록"은 내부 구조가 안정적인 템플릿의 일부입니다.
이 경우 전체 템플릿에는 v-if 및 v-for와 같은 구조적 지시문이 포함되어 있지 않기 때문에 단일 블록이 있습니다.
동적 바인딩 여부!
<div> <!-- root block -->
<div>...</div> <!-- not tracked -->
<div :id="id"></div> <!-- tracked -->
<div> <!-- not tracked -->
<div>{{ bar }}</div> <!-- tracked -->
</div>
</div>
div (block root)
- div with :id binding
- div with {{ bar }} binding
<div> <!-- root block -->
<div>
<div v-if> <!-- if block -->
...
<div>
</div>
</div>
자식 블록은 부모 블록의 동적 children 배열 내에서 추적됩니다.
이것은 상위 블록에 대한 안정적인 구조를 유지합니다.
Impact on SSR Hydration
- 단일 요소 수화는 해당 vnode의 패치 플래그를 기반으로 빠른 경로를 취할 수 있습니다.
- 블록 노드와 동적인 children만 수화 도중에 방문하면 템플릿 수준 부분 수화를 효과적으로 달성할 수 있습니다.
'FrontEnd' 카테고리의 다른 글
Vue.js 커스텀 Directive 만들어보기 (0) | 2022.10.07 |
---|---|
Vue3과 React 훅의 반응성 비교 : 불변 VS 가변 (0) | 2022.10.06 |
Vue3 Reactivity In Depth (뷰3의 반응성 원리 이해하기) (0) | 2022.10.06 |
RxJS와 반응형 프로그래밍 (0) | 2022.10.06 |
디자인시스템 토큰 이름붙이기 [Naming Tokens in Design Systems] (0) | 2022.10.05 |