Vue 3 內部原理講解,深刻理解 Vue 3,建立你本身的 Vue 3。
Deep Dive into Vue 3. Build Your Own Vue 3 From Scratch.html
本文完整內容可見,從零開始建立你本身的Vue 3vue
你能學到什麼node
Vue 3有三個核心模塊,分別是:react
reactivity
)模塊compiler
)模塊renderer
)模塊reactivity
模塊用來建立響應式對象,咱們能夠監聽這些對象的修改,當執使用了這些響應式對象的代碼執行時,他們就會被跟蹤,當響應式對象的值發生了變化時,這些被追蹤的代碼會從新執行。git
compiler
模塊是用來處理模板的,它把模板編譯成 render
函數,它能夠發生在瀏覽器的運行階段,但更多的是在Vue項目構建時進行編譯。github
renderer
模塊處理 VNode
,把組件渲染到 web 頁上。它包含三個階段:web
Render
階段,調用 render
函數並返回一個 VNode
;Mount
階段,render
接收 VNode
,而後進行 JavaScript DOM 操做來建立 web 頁;Patch
階段,render
接收新舊兩個 VNode
,比較兩者的不一樣,而後進行頁面的局部更新。compiler
把 HTML 編譯成 render
函數;reactivity
模塊初始化 reactive
對象;renderer
模塊的 Render
階段,調用引用了 reactive
對象的 render
函數,這樣就監聽了響應式對象,render
函數返回 VNode
;renderer
模塊的 Monut
階段,用 VNode
生成真實的 DOM 並渲染到頁面上;reactive
對象發生了變化,將再次調用 render
函數建立新的 VNode
,這時將進入 renderer
模塊的 Patch
階段,更新頁面的變化。你能學到什麼算法
render
函數存在的必要性tempalte
和 render
的使用場景Virtual DOM 就是用 JavaScript 對象來描述真實的 DOM 節點。typescript
例如,有這樣一段 HTML:編程
<div id="div"> <button @click="click">click</button> </div>
用 Virtual DOM 來表示能夠是這樣的(爲何說能夠是這樣,由於這徹底取決於你的設計):
const vDom = { tag: 'div', id: 'div', children: [{ tag: 'button', onClick: this.click }] }
render
函數,把 Virtual DOM 渲染成本身想要的東西,而不只僅是 DOM。首先咱們知道,當你在編寫 Vue 組件或頁面時,通常會提供一個 template
選項來寫 HTML 內容。根據Vue 3 總覽這一部份內容的介紹,Vue 會先走編譯階段,把 template
編譯成 render
函數,因此說最終的 DOM 必定是從 render
函數輸出的。所以 render
函數能夠用來代替 template
,它返回的內容就是 VNode
。直接使用 render
反而能夠省去 complier
過程。
Vue 中提供的 render
函數是很是有用的,由於有些狀況用 template
來表達業務邏輯會必定程度受到限制,這種狀況你須要一種比較靈活的編程方式來表達底層的邏輯。
例如,當你有一個需求是大量的文本輸入框,這中需求你要寫的標籤並很少,可是卻揉了大量的交互邏輯,你須要在模板上添加大量的邏輯代碼(好比控制關聯標籤的顯示),然而,你的 JavaScript 代碼中也有大量的邏輯代碼。
render
函數的存在可讓你在一個地方寫業務邏輯,這時你就不用太多的去考慮標籤的問題了。
有一段 template
以下:
template: '<div id="foo" @click="onClick">hello</div>'
Vue 3 中用 render
函數實現以下:
import { h } from 'vue' render() { return h('div', { id: 'foo', onClick: this.onClick }, 'hello') }
因爲這是純粹的 JavaScript,因此若是你須要實現 template
中相似 v-if
、v-for
這樣的功能,直接經過三元表達式作到。
import { h } from 'vue' render() { let nodeToReturn // v-if="ok" if(this.ok) { nodeToReturn = h('div', { id: 'foo', onClick: this.onClick }, 'ok') } else { // v-for="item in list" const children = this.list.map(item => { return h('p', { key: item.id }, item.text) }) nodeToReturn = h('div', {}, children) } return nodeToReturn }
這就是 render
基本使用用法,就是 JavaScript 代碼而已。
通常來講咱們用 tempate
能夠知足大多數場景來,可是你必定了解過 slot
這個東西,若是隻使用 tempate
你將沒法操做 slot
中的內容,若是你須要程序式地修改傳進來的 slot
內容,你就必須用到 render
函數了(這也是大多數使用 render
函數的場景)。
下面咱們用一個例子來講明。
好比咱們要實現這樣一個組件:實現層級縮進效果,即相似 HTML 中嵌套的 UL 標籤,看起來就像這樣:
level 1 level 1-1 level 1-2 level 1-2-1 level 1-2-2
咱們的模板是這樣寫的,實際上 Stack
組件就是給每個 slot
都增長一個左邊距:
<Stack size="10"> <div>level 1</div> <Stack size="10"> <div>level 1-1</div> <div>level 1-2</div> <Stack size="10"> <div>level 1-2-1</div> <div>level 1-2-2</div> </Stack> </Stack> </Stack>
如今咱們只用 template
是沒法實現這種效果的,衆所周知,template
只能把默認的 slot
渲染出來,它不能程序式處理 slot
的值。
咱們先用 template
來實現這個組件,stack.html:
const Stack = { props: { size: [String, Number] }, template: ` <div class="stack"> <slot></slot> </div> ` }
這樣因爲不能處理 slot
內容,那麼它的表現效果以下,並無層級縮進:
level 1 level 1-1 level 1-2 level 1-2-1 level 1-2-2
咱們如今嘗試用 render
函數實現 Stack
組件:
const { h } = Vue const Stack = { props: { size: [String, Number] }, render() { const slot = this.$slots.default ? this.$slots.default() : [] return h('div', { class: 'stack' }, // 這裏給每一項 slot 增長一個縮進 class slot.map(child => { return h('div', { class: `ml${this.$props.size}` }, [ child ]) })) } }
render
函數中咱們能夠經過 this.$slots
拿到插槽內容,經過 JavaScript 把它處理成任何咱們想要的東西,這裏咱們給每一項 slot
添加了一個 margin-left: 10px
縮進,看下效果:
level 1 level 1-1 level 1-2 level 1-2-1 level 1-2-2
完美,咱們實現了一個用 template
幾乎實現不了的功能。
原則:
render
render
函數template
,這樣更高效,且 template
更容易被 complier
優化你可能會想,爲何不直接編譯成 VNode
,而要在中間加一層 render
呢?
這是由於 VNode
自己包含的信息比較多,手寫太麻煩,也許你寫着寫着不自覺就封裝成了一個 helper
函數,h
函數就是這樣的,它把公用、靈活、複雜的邏輯封裝成函數,並交給運行時,使用這樣的函數將大大下降你的編寫成本。
知道了爲何要有 render
後,才須要去設計實現它,其實主要是實現 h
函數。
你能學到什麼
VNode
render
的具體渲染原理diff
算法的做用在Vue 3總覽章節中,咱們已經初步認識了編譯器(complier
)和渲染器( renderer
)的做用。
render
函數VNode
,把組件渲染到 web 頁上咱們有這樣一段 HTML:
<div id="div"> <button @click="click">click</button> </div>
編譯器會先把它處理成 render
函數,相似下面的代碼:
import { h } from 'vue' render() { return h('div', { id: 'div', }, [ h('button', { onClick: this.click }, 'click') ]) }
渲染器經過 render
函數獲取對應的 VNode
,相似這樣:
const vDom = { tag: 'div', id: 'div', children: [{ tag: 'button', onClick: this.click, text: 'click' }] }
上面是一個很簡單的例子,實際上,Vue 3中的編譯器作了不少的優化工做,好比判斷你的節點是靜態的仍是動態的、緩存事件的綁定等等。因此若是你的組件用 template
實現的話,反而會被 Vue 優化。
咱們經過 Vue 3在線模板編譯系統 生成一段真實代碼:
import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", { id: "div" }, [ _createVNode("button", { onClick: _ctx.click }, "click", 8 /* PROPS */, ["onClick"]) ])) } // Check the console for the AST
能夠看到和咱們手寫的 render
函數仍是有比較大的差別。
render
函數返回結果就是 h
函數執行的結果,所以 h
函數的輸出爲 VNode
。
因此須要先設計一下咱們的 VNode
。
一個 html
標籤有它的標籤名、屬性、事件、樣式、子節點等諸多信息,這些內容都須要在 VNode
中體現。
<div id="div"> div text <p>p text</p> </div>
const elementVNode = { tag: 'div', props: { id: 'div' }, text: 'div text', children: [{ tag: 'p', props: null, text: 'p text' }] }
上面的代碼顯示了 DOM 變成 VNode
的表現形式,VNode
各屬性解釋:
tag
:表示 DOM 元素的標籤名,如 div
、span
等props
:表示 DOM 元素上的屬性,如id
、class
等children
:表示 DOM 元素的子節點text
:表示 DOM 元素的文本節點這樣設計 VNode
徹底沒有問題(實際上 Vue 2 就是這樣設計的),可是 Vue 3 設計的 VNode
並不包含 text
屬性,而是直接用 children
代替,由於 text
本質也是 DOM 的子節點。
在保證語義講得通的狀況下儘量複用屬性,可使 VNode
對象更加輕量。
基於此咱們把剛纔的 VNode
修改爲以下形式:
const elementVNode = { tag: 'div', props: { id: 'div' }, children: [{ tag: null, props: null, children: 'div text' }, { tag: 'p', props: null, children: 'p text' }] }
什麼是抽象內容呢?組件就屬於抽象內容,好比下面這一段模板內容:
<div> <MyComponent></MyComponent> </div>
MyComponent
是一個組件,咱們預期渲染出 MyComponent
組件全部的內容,而不是一個 MyComponent
標籤,這用 VNode
如何表示呢?
上一段內容咱們其實已經經過 tag
是否爲 null
來區分元素節點和文本節點了,那這裏咱們能夠經過 tag
是不是字符串判斷是標籤仍是組件呢?
const elementVNode = { tag: 'div', props: null, children: [{ tag: MyComponent, props: null }] }
理論上是能夠的,Vue 2 中就是經過 tag
來判斷的,具體過程以下,能夠在這裏看源碼:
VNode.tag
若是不是字符串,則建立組件類型的 VNode
VNode.tag
是字符串
html
或 svg
標籤,則建立正常的 VNode
VNode
VNode
以上這些判斷都是在掛載(或 patch
)階段進行的,換句話說,一個 VNode
表示的內容須要在代碼運行階段才知道。這就帶來了兩個難題:沒法從 AOT
的層面優化、開發者沒法手動優化。
若是能夠提早知道 VNode
類型,那麼就能夠對其進行優化,因此這裏咱們能夠定義好一套用來判斷 VNode
類型的規則,隨即是用 FLAG = 1
這樣的數字表示仍是其它方法。
這裏咱們給 VNode
增長一個字段 shapeFlag
(這是爲了和 Vue 3 保持一致),它是一個枚舉類型變量,具體以下:
export const enum ShapeFlags { // html 或 svg 標籤 ELEMENT = 1, // 函數式組件 FUNCTIONAL_COMPONENT = 1 << 1, // 普通有狀態組件 STATEFUL_COMPONENT = 1 << 2, // 子節點是純文本 TEXT_CHILDREN = 1 << 3, // 子節點是數組 ARRAY_CHILDREN = 1 << 4, // 子節點是 slots SLOTS_CHILDREN = 1 << 5, // Portal PORTAL = 1 << 6, // Suspense SUSPENSE = 1 << 7, // 須要被keepAlive的有狀態組件 COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 已經被keepAlive的有狀態組件 COMPONENT_KEPT_ALIVE = 1 << 9, // 有狀態組件和函數式組件都是「組件」,用 COMPONENT 表示 COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT }
如今咱們能夠修改咱們的 VNode
以下:
const elementVNode = { shapeFlag: ShapeFlags.ELEMENT, tag: 'div', props: null, children: [{ shapeFlag: ShapeFlags.COMPONENT, tag: MyComponent, props: null }] }
shapeFlag
如何用來判斷 VNode
類型呢?按位運算便可。
const isComponent = vnode.shapeFlag & ShapeFlags.COMPONENT
熟悉一下按位運算。
a & b
:對於每個比特位,只有兩個操做數相應的比特位都是1時,結果才爲1,不然爲0。a | b
:對於每個比特位,當兩個操做數相應的比特位至少有一個1時,結果爲1,不然爲0。咱們把 ShapeFlags
對應的值列出來,以下:
ShapeFlags | 操做 | bitmap |
---|---|---|
ELEMENT | 0000000001 |
|
FUNCTIONAL_COMPONENT | 1 << 1 | 000000001 0 |
STATEFUL_COMPONENT | 1 << 2 | 00000001 00 |
TEXT_CHILDREN | 1 << 3 | 0000001 000 |
ARRAY_CHILDREN | 1 << 4 | 000001 0000 |
SLOTS_CHILDREN | 1 << 5 | 00001 00000 |
PORTAL | 1 << 6 | 0001 000000 |
SUSPENSE | 1 << 7 | 001 0000000 |
COMPONENT_SHOULD_KEEP_ALIVE | 1 << 8 | 01 00000000 |
COMPONENT_KEPT_ALIVE | 1 << 9 | 1 000000000 |
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
根據上表展現的基本 flags
值能夠很容易地得出下表:
ShapeFlags | bitmap |
---|---|
COMPONENT | 00000001 1 0 |
上面咱們已經看到了 children
能夠是數組或純文本,但真實場景多是:
null
這裏咱們能夠增長一個 ChildrenShapeFlags
的變量表示 children
的類型,可是基於以前的設計原則,咱們徹底能夠用 ShapeFlags
來表示,那麼同一個 ShapeFlags
如何既用來表示 VNode
的類型,又用來表示其 children
的類型呢?
仍然是按位運算,咱們經過 JavaScript 代碼判斷 children
類型,而後和當前 VNode
進行按位或運算便可。
咱們增長以下函數用來專門處理子節點類型,這和 Vue 3 中的處理一致:
function normalizeChildren(vnode, children) { let type = 0 if (children == null) { children = null } else if (Array.isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN } else if (typeof children === 'string') { children = String(children) type = ShapeFlags.TEXT_CHILDREN } vnode.shapeFlag |= type }
這樣咱們就能夠直接經過 shapeFlag
同時判斷 VNode
及其 children
類型了。
爲何children
也須要標識呢?緣由只有一個: 爲了patch
過程的優化。
至此,咱們能夠定義 VNode
結構以下:
export interface VNodeProps { [key: string]: any } export interface VNode { // _isVNode 是 VNode 對象 _isVNode: true // el VNode 對應的真實 DOM el: Element | null shapeFlag: ShapeFlags.ELEMENT, tag: | string | Component | null, props: VNodeProps | null, children: string | Array<VNode> }
實際上,Vue 3 中對 VNode
的定義要複雜的多,這裏就不去細看了。
首先咱們實現一個最簡單的 h
函數,能夠是這樣的,接收三個參數:
tag
標籤名props
DOM 上的屬性children
子節點咱們新建一個文件 h.ts
,內容以下:
function h(tag, props, children){ return { tag, props, children } }
咱們用以下的 VNode
來表示 <div class="red"><span>hello</span></div>
:
import { h } from './h' const vdom = h('div', { class: 'red' }, [ h('span', null, 'hello') ])
看一下實際輸出內容:
const vdom = { "tag": "div", "props": { "class": "red" }, "children": [ { "tag": "span", "props": null, "children": "hello" } ] }
基本符合預期,可是這裏有同窗可能又要問了:「這個 vdom
和寫的 h
函數沒什麼不一樣,爲何不直接寫 VNode
?」
這是由於咱們如今的 h
函數所作的僅僅就是返回傳入的參數,實際上根據咱們對 VNode
的定義,還缺乏一些字段,不過你也能夠直接寫 VNode
,但這樣會增長大量的額外工做。
如今咱們補全 h
函數,添加 _isVNode
、el
和 shapeFlag
字段。
function h(tag, props = null, children = null) { return { _isVNode: true, el: null, shapeFlag: null, tag, props, children } }
這裏的 _isVNode
永遠爲 true
,el
不是在建立 VNode
的時候賦值,因此不用處理,咱們主要處理 shapeFlag
,實際上 shapeFlag
有 10 種類型,咱們這裏只實現一個最簡單的判斷:
function h(tag, props = null, children = null) { let shapeFlag = null // 這裏爲了簡化,直接這樣判斷 if (typeof tag === 'string') { shapeFlag = ShapeFlags.ELEMENT } else if(typeof tag === 'object'){ shapeFlag = ShapeFlags.STATEFUL_COMPONENT } else if(typeof tag === 'function'){ shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT } return { _isVNode: true, el: null, shapeFlag, tag, props, children } }
如今咱們須要處理一下 children
的類型了,VNode 章節中咱們講過其判斷邏輯,那麼 h
函數如今完整邏輯以下:
function h(tag, props = null, children = null) { let shapeFlag = null // 這裏爲了簡化,直接這樣判斷 if (typeof tag === 'string') { shapeFlag = ShapeFlags.ELEMENT } else if(typeof tag === 'object'){ shapeFlag = ShapeFlags.STATEFUL_COMPONENT } else if(typeof tag === 'function'){ shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT } const vnode = { _isVNode: true, el: null, shapeFlag, tag, props, children } normalizeChildren(vnode, vnode.children) return vnode } function normalizeChildren(vnode, children) { let type = 0 if (children == null) { children = null } else if (Array.isArray(children)) { type = ShapeFlags.ARRAY_CHILDREN } else if (typeof children === 'object') { type = ShapeFlags.SLOTS_CHILDREN } else if (typeof children === 'string') { children = String(children) type = ShapeFlags.TEXT_CHILDREN } vnode.shapeFlag |= type }
如今咱們從新寫一個測試代碼看一下 h
函數輸入結果:
import { h } from './h' const MyComponent = { render() {} } const vdom = h('div', { class: 'red' }, [ h('p', null, 'hello'), h('p', null, null), h(MyComponent) ]) console.log(vdom); // vdom: // { // _isVNode: true, // el: null, // shapeFlag: 17, // tag: 'div', // props: { class: 'red' }, // children: [ // { // _isVNode: true, // el: null, // shapeFlag: 9, // tag: 'p', // props: null, // children: 'hello' // }, // { // _isVNode: true, // el: null, // shapeFlag: 1, // tag: 'p', // props: null, // children: null // }, // { // _isVNode: true, // el: null, // shapeFlag: 4, // tag: [Object], // props: null, // children: null // } // ] // }
至此已經完成了 h
函數的基本設計,能夠獲得想要的 VNode
了,下一步就是把 VNode
渲染到頁面上。
獲得 VNode
以後,咱們須要把它渲染到頁面上,這就是渲染器的 Mount
階段。
首先,新建一個 render.ts
文件,用來處理掛載相關代碼。
mount
函數應該是這樣,接收一個 VNode
做爲參數,並把生成的 DOM 放進指定的容器 container
中,實現以下:
function mount(vnode, container) { const el = document.createElement(vnode.tag) contianer.appendChild(el); }
這就是掛載所作的核心事情,不過這裏咱們還缺乏具體要實現的內容:
shapeFlag
生成不一樣 DOMvnode.el
VNode
的類型問題這裏咱們須要先了解一下普通有狀態組件和函數式組件分別是什麼,如下僅作理解用。
const MyComponent = { render() { return h('div', null, 'stateful component') } }
function MyFunctionalComponent() { return h('div', null, 'function component') }
咱們根據 vnode.shapeFlag
的值來對各類類型 VNode
進行渲染操做。
function mount(vnode, container) { if (vnode.tag === null) { mountTextElement(vnode, container) } else if (vnode.shapeFlag & ShapeFlags.ELEMENT) { mountElement(vnode, container) } else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { mountStatefulComponent(vnode, container) } else if (vnode.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) { mountFunctionalComponent(vnode, container) } } function mountTextElement(vnode, container) { ... } function mountElement(vnode, container) { ... } function mountStatefulComponent(vnode, container) { ... } function mountFunctionalComponent(vnode, container) { ... }
function mountTextElement(vnode, container) { const el = document.createTextNode(vnode.children) container.appendChild(el) }
function mountElement(vnode, container) { const el = document.createElement(vnode.tag) container.appendChild(el) }
普通有狀態組件就是一個對象,經過 render
返回其 VNode
, 所以其渲染方法以下:
function mountStatefulComponent(vnode, container) { const instance = vnode.tag instance.$vnode = instance.render() mount(instance.$vnode, container) instance.$el = vnode.el = instance.$vnode.el }
函數式組件的 tag
爲一個函數,返回值爲 VNode
,所以其渲染方法以下:
function mountFunctionalComponent(vnode, container){ const $vnode = vnode.tag() mount($vnode, container) vnode.el = $vnode.el }
這裏爲了簡化,這裏咱們假設 props
的每一項都是 DOM 的 attribute
,因此咱們能夠這樣作:
function mountElement(vnode, container) { const el = document.createElement(vnode.tag) if(vnode.props){ for(const key in vnode.props){ const value = vnode.props[key] el.setAttribute(key, value) } } container.appendChild(el); }
實際上,Vue 3 中 props
是一個扁平化的結構,它同時包含了 property
、attribute
、event listener
等,每一項都須要單獨處理,以下:
props: { id: 'div', class: 'red', key: 'key1', onClick: this.onClick }
簡單解釋 property
、attribute
的區別就是:attribute
是 DOM 自帶的屬性,如:id
、class
;property
是自定義的屬性名,如:key
、data-xxx
。
咱們知道 children
能夠是字符串或數組,所以實現方法以下:
function mountElement(vnode, container) { const el = document.createElement(vnode.tag) // props if(vnode.props){ for(const key in vnode.props){ const value = vnode.props[key] el.setAttribute(key, value) } } // children if(vnode.children){ if(typeof vnode.children === 'string'){ el.textContent = vnode.children }else{ vnode.children.forEach(child => { mount(child, el) }) } } container.appendChild(el); }
VNode
及其 DOM這個只須要增長一行代碼便可,其它函數相似:
function mountTextElement(vnode, container) { const el = document.createTextNode(vnode.children) vnode.el = el // (*) container.appendChild(el) }
如今咱們實現了渲染器 mount
全部的功能,完整代碼以下:
function mount(vnode, container) { if (vnode.tag === null) { mountTextElement(vnode, container) } else if (vnode.shapeFlag & ShapeFlags.ELEMENT) { mountElement(vnode, container) } else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { mountStatefulComponent(vnode, container) } else if (vnode.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) { mountFunctionalComponent(vnode, container) } } function mountTextElement(vnode, container) { const el = document.createTextNode(vnode.children) vnode.el = el container.appendChild(el) } function mountElement(vnode, container) { const el = document.createElement(vnode.tag) // props if(vnode.props){ for(const key in vnode.props){ const value = vnode.props[key] el.setAttribute(key, value) } } // children if(vnode.children){ if(typeof vnode.children === 'string'){ el.textContent = vnode.children }else{ vnode.children.forEach(child => { mount(child, el) }) } } vnode.el = el container.appendChild(el) } function mountStatefulComponent(vnode, container) { const instance = vnode.tag instance.$vnode = instance.render() mount(instance.$vnode, container) instance.$el = vnode.el = instance.$vnode.el } function mountFunctionalComponent(vnode, container){ const $vnode = vnode.tag() mount($vnode, container) vnode.el = $vnode.el }
如今咱們能夠檢驗一下寫的是否正確,新建 vdom.html,添加以下代碼,並在瀏覽器中打開:
import { h } from './h' import { mount } from './render' const MyComponent = { render() { return h('div', null, 'stateful component') } } function MyFunctionalComponent() { return h('div', null, 'function component') } const vdom = h('div', { class: 'red' }, [ h('p', null, 'text children'), h('p', null, null), h(MyComponent), h(MyFunctionalComponent) ]) console.log(vdom); mount(vdom, document.querySelector("#app"))
瀏覽器渲染結果,全部內容均正常顯示。
至此,咱們已經瞭解了 Vue 3 的基本渲染原理,並實現了一個簡易版本的渲染器。
未完待續~(因爲內容太多,後續將不在本文繼續增長)
你能學到什麼
reactive
設計理念本文完整內容可見,build-your-own-vue-next