前幾天想學(xué)學(xué)Vue中怎么編寫可復(fù)用的組件,提到要對Vue的render
函數(shù)有所了解??勺屑?xì)一想,對于Vue的render
函數(shù)自己只是看了官方的一些介紹,并未深入一點(diǎn)去了解這方面的知識。為了更好的學(xué)習(xí)后續(xù)的知識,又折回來了解Vue中的render
函數(shù),這一切主要都是為了后續(xù)能更好的學(xué)習(xí)Vue的知識。
今天我們學(xué)習(xí)的目的是了解和學(xué)習(xí)Vue的render
函數(shù)。如果想要更好的學(xué)習(xí)Vue的render
函數(shù)相關(guān)的知識,我們有必要重溫一下Vue中的一些基本概念。那么先上一張圖,這張圖從宏觀上展現(xiàn)了Vue整體流程:
從上圖中,不難發(fā)現(xiàn)一個(gè)Vue的應(yīng)用程序是如何運(yùn)行起來的,模板通過編譯生成AST,再由AST生成Vue的render
函數(shù)(渲染函數(shù)),渲染函數(shù)結(jié)合數(shù)據(jù)生成Virtual DOM樹,Diff和Patch后生成新的UI。從這張圖中,可以接觸到Vue的一些主要概念:
watcher
,這個(gè)watcher
將會(huì)在組件render
的時(shí)候收集組件所依賴的數(shù)據(jù),并在依賴有更新的時(shí)候,觸發(fā)組件重新渲染。你根本不需要寫shouldComponentUpdate
,Vue會(huì)自動(dòng)優(yōu)化并更新要更新的UI。上圖中,render
函數(shù)可以作為一道分割線,render
函數(shù)的左邊可以稱之為編譯期,將Vue的模板轉(zhuǎn)換為渲染函數(shù)。render
函數(shù)的右邊是Vue的運(yùn)行時(shí),主要是基于渲染函數(shù)生成Virtual DOM樹,Diff和Patch。
Vue推薦在絕大多數(shù)情況下使用template
來創(chuàng)建你的HTML。然而在一些場景中,需要使用JavaScript的編程能力和創(chuàng)建HTML,這就是render
函數(shù),它比template
更接近編譯器。
<h1> <a name="hello-world" href="#hello-world"> Hello world! </a></h1>
在HTML層,我們決定這樣定義組件接口:
<anchored-heading :level="1">Hello world!</anchored-heading>
當(dāng)我們開始寫一個(gè)通過level
的prop
動(dòng)態(tài)生成heading
標(biāo)簽的組件,你可能很快想到這樣實(shí)現(xiàn):
<!-- HTML --><script type="text/x-template" id="anchored-heading-template"> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6></script><!-- Javascript -->Vue.component('anchored-heading', { template: '#anchored-heading-template', props: { level: { type: Number, required: true } }})
在這種場景中使用 template
并不是最好的選擇:首先代碼冗長,為了在不同級別的標(biāo)題中插入錨點(diǎn)元素,我們需要重復(fù)地使用 <slot></slot>
。
雖然模板在大多數(shù)組件中都非常好用,但是在這里它就不是很簡潔的了。那么,我們來嘗試使用 render
函數(shù)重寫上面的例子:
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // tag name 標(biāo)簽名稱 this.$slots.default // 子組件中的陣列 ) }, props: { level: { type: Number, required: true } }})
簡單清晰很多!簡單來說,這樣代碼精簡很多,但是需要非常熟悉 Vue 的實(shí)例屬性。在這個(gè)例子中,你需要知道當(dāng)你不使用 slot
屬性向組件中傳遞內(nèi)容時(shí),比如 anchored-heading
中的 Hello world!
,這些子元素被存儲在組件實(shí)例中的 $slots.default
中。
對Vue的一些概念和渲染函數(shù)的基礎(chǔ)有一定的了解之后,我們需要對一些瀏覽器的工作原理有一些了解,這樣對我們學(xué)習(xí)render
函數(shù)是很重要的。比如下面的這段HTML代碼:
<div> <h1>My title</h1> Some text content <!-- TODO: Add tagline --></div>
當(dāng)瀏覽器讀到這些代碼時(shí),它會(huì)建立一個(gè)DOM節(jié)點(diǎn)樹來保持追蹤,如果你會(huì)畫一張家譜樹來追蹤家庭成員的發(fā)展一樣。
HTML的DOM節(jié)點(diǎn)樹如下圖所示:
每個(gè)元素都是一個(gè)節(jié)點(diǎn)。每片文字也是一個(gè)節(jié)點(diǎn)。甚至注釋也都是節(jié)點(diǎn)。一個(gè)節(jié)點(diǎn)就是頁面的一個(gè)部分。就像家譜樹一樣,每個(gè)節(jié)點(diǎn)都可以有孩子節(jié)點(diǎn) (也就是說每個(gè)部分可以包含其它的一些部分)。
高效的更新所有這些節(jié)點(diǎn)會(huì)是比較困難的,不過所幸你不必再手動(dòng)完成這個(gè)工作了。你只需要告訴 Vue 你希望頁面上的 HTML 是什么,這可以是在一個(gè)模板里:
<h1>{{ blogTitle }}</h1>
或者一個(gè)渲染函數(shù)里:
render: function (createElement) { return createElement('h1', this.blogTitle)}
在這兩種情況下,Vue 都會(huì)自動(dòng)保持頁面的更新,即便 blogTitle
發(fā)生了改變。
在Vue 2.0中,渲染層的實(shí)現(xiàn)做了根本性改動(dòng),那就是引入了虛擬DOM。
Vue的編譯器在編譯模板之后,會(huì)把這些模板編譯成一個(gè)渲染函數(shù)。而函數(shù)被調(diào)用的時(shí)候就會(huì)渲染并且返回一個(gè)虛擬DOM的樹。
當(dāng)我們有了這個(gè)虛擬的樹之后,再交給一個(gè)Patch函數(shù),負(fù)責(zé)把這些虛擬DOM真正施加到真實(shí)的DOM上。在這個(gè)過程中,Vue有自身的響應(yīng)式系統(tǒng)來偵測在渲染過程中所依賴到的數(shù)據(jù)來源。在渲染過程中,偵測到數(shù)據(jù)來源之后就可以精確感知數(shù)據(jù)源的變動(dòng)。到時(shí)候就可以根據(jù)需要重新進(jìn)行渲染。當(dāng)重新進(jìn)行渲染之后,會(huì)生成一個(gè)新的樹,將新的樹與舊的樹進(jìn)行對比,就可以最終得出應(yīng)施加到真實(shí)DOM上的改動(dòng)。最后再通過Patch函數(shù)施加改動(dòng)。
簡單點(diǎn)講,在Vue的底層實(shí)現(xiàn)上,Vue將模板編譯成虛擬DOM渲染函數(shù)。結(jié)合Vue自帶的響應(yīng)系統(tǒng),在應(yīng)該狀態(tài)改變時(shí),Vue能夠智能地計(jì)算出重新渲染組件的最小代價(jià)并應(yīng)到DOM操作上。
Vue支持我們通過data
參數(shù)傳遞一個(gè)JavaScript對象做為組件數(shù)據(jù),然后Vue將遍歷此對象屬性,使用Object.defineProperty
方法設(shè)置描述對象,通過存取器函數(shù)可以追蹤該屬性的變更,Vue創(chuàng)建了一層Watcher
層,在組件渲染的過程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的setter
被調(diào)用時(shí),會(huì)通知Watcher
重新計(jì)算,從而使它關(guān)聯(lián)的組件得以更新,如下圖:
有關(guān)于Vue的響應(yīng)式相關(guān)的內(nèi)容,可以閱讀下列文章:
Object.defineproperty
對于Vue自帶的響應(yīng)式系統(tǒng),并不是咱們今天要聊的東西。我們還是回到Vue的虛擬DOM中來。對于虛擬DOM,咱們來看一個(gè)簡單的實(shí)例,就是下圖所示的這個(gè),詳細(xì)的闡述了模板 → 渲染函數(shù) → 虛擬DOM樹 → 真實(shí)DOM
的一個(gè)過程
其實(shí)Vue中的虛擬DOM還是很復(fù)雜的,我也是一知半解,如果你想深入的了解,可以閱讀@JoeRay61的《Vue原理解析之Virtual DOM》一文。
通過前面的學(xué)習(xí),我們初步了解到Vue通過建立一個(gè)虛擬DOM對真實(shí)DOM發(fā)生的變化保持追蹤。比如下面這行代碼:
return createElement('h1', this.blogTitle)
createElement
到底會(huì)返回什么呢?其實(shí)不是一個(gè)實(shí)際的 DOM 元素。它更準(zhǔn)確的名字可能是 createNodeDescription
,因?yàn)樗男畔?huì)告訴 Vue 頁面上需要渲染什么樣的節(jié)點(diǎn),及其子節(jié)點(diǎn)。我們把這樣的節(jié)點(diǎn)描述為“虛擬節(jié)點(diǎn) (Virtual Node)”,也常簡寫它為“VNode”。“虛擬 DOM”是我們對由 Vue 組件樹建立起來的整個(gè) VNode 樹的稱呼。
Vue組件樹建立起來的整個(gè)VNode樹是唯一的。這意味著,下面的render
函數(shù)是無效的:
render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 錯(cuò)誤-重復(fù)的 VNodes myParagraphVNode, myParagraphVNode ])}
如果你真的需要重復(fù)很多次的元素/組件,你可以使用工廠函數(shù)來實(shí)現(xiàn)。例如,下面這個(gè)例子 render
函數(shù)完美有效地渲染了 20
個(gè)重復(fù)的段落:
render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) )}
上圖展示的是獨(dú)立構(gòu)建時(shí)的一個(gè)渲染流程圖。
繼續(xù)使用上面用到的模板到真實(shí)DOM過程的一個(gè)圖:
這里會(huì)涉及到Vue的另外兩個(gè)概念:
HTML字符串 → render函數(shù) → VNode → 真實(shí)DOM節(jié)點(diǎn)
render函數(shù) → VNode → 真實(shí)DOM節(jié)點(diǎn)
運(yùn)行時(shí)構(gòu)建的包,會(huì)比獨(dú)立構(gòu)建少一個(gè)模板編譯器。在$mount
函數(shù)上也不同。而$mount
方法又是整個(gè)渲染過程的起始點(diǎn)。用一張流程圖來說明:
由此圖可以看到,在渲染過程中,提供了三種渲染模式,自定義render
函數(shù)、template
、el
均可以渲染頁面,也就是對應(yīng)我們使用Vue時(shí),三種寫法:
render函數(shù)
Vue.component('anchored-heading', { render: function (createElement) { return createElement ( 'h' + this.level, // tag name標(biāo)簽名稱 this.$slots.default // 子組件中的陣列 ) }, props: { level: { type: Number, required: true } }})
template
寫法let app = new Vue({ template: `<div>{{ msg }}</div>`, data () { return { msg: '' } }})
el
寫法let app = new Vue({ el: '#app', data () { return { msg: 'Hello Vue!' } }})
這三種渲染模式最終都是要得到render
函數(shù)。只不過用戶自定義的render
函數(shù)省去了程序分析的過程,等同于處理過的render
函數(shù),而普通的template
或者el
只是字符串,需要解析成AST,再將AST轉(zhuǎn)化為render
函數(shù)。
記住一點(diǎn),無論哪種方法,都要得到
render
函數(shù)。
我們在使用過程中具體要使用哪種調(diào)用方式,要根據(jù)具體的需求來。
如果是比較簡單的邏輯,使用template
和el
比較好,因?yàn)檫@兩種都屬于聲明式渲染,對用戶理解比較容易,但靈活性比較差,因?yàn)樽罱K生成的render
函數(shù)是由程序通過AST解析優(yōu)化得到的;而使用自定義render
函數(shù)相當(dāng)于人已經(jīng)將邏輯翻譯給程序,能夠勝任復(fù)雜的邏輯,靈活性高,但對于用戶的理解相對差點(diǎn)。
createElement
在使用render
函數(shù),其中還有另一個(gè)需要掌握的部分,那就是createElement
。接下來我們需要熟悉的是如何在createElement
函數(shù)中生成模板。那么我們分兩個(gè)部分來對createElement
進(jìn)行理解。
createElement
參數(shù)createElement
可以是接受多個(gè)參數(shù):
{String | Object | Function}
第一個(gè)參數(shù)對于createElement
而言是一個(gè)必須的參數(shù),這個(gè)參數(shù)可以是字符串string
、是一個(gè)對象object
,也可以是一個(gè)函數(shù)function
。
<div id="app"> <custom-element></custom-element></div>Vue.component('custom-element', { render: function (createElement) { return createElement('div') }})let app = new Vue({ el: '#app'})
上面的示例,給createElement
傳了一個(gè)String
參數(shù)'div'
,即傳了一個(gè)HTML標(biāo)簽字符。最后會(huì)有一個(gè)div
元素渲染出來:
接著把上例中的String
換成一個(gè)Object
,比如:
Vue.component('custom-element', { render: function (createElement) { return createElement({ template: `<div>Hello Vue!</div>` }) }})
上例傳了一個(gè){template: '<div>Hello Vue!</div>'}
對象。此時(shí)custom-element
組件渲染出來的結(jié)果如下:
除此之外,還可以傳一個(gè)Function
,比如:
Vue.component('custom-element', { render: function (createElement) { var eleFun = function () { return { template: `<div>Hello Vue!</div>` } } return createElement(eleFun()) }})
最終得到的結(jié)果和上圖是一樣的。這里傳了一個(gè)eleFun()
函數(shù)給createElement
,而這個(gè)函數(shù)返回的是一個(gè)對象。
{Object}
createElement
是一個(gè)可選參數(shù),這個(gè)參數(shù)是一個(gè)Object
。來看一個(gè)小示例:
<div id="app"> <custom-element></custom-element></div>Vue.component('custom-element', { render: function (createElement) { var self = this // 第一個(gè)參數(shù)是一個(gè)簡單的HTML標(biāo)簽字符 “必選” // 第二個(gè)參數(shù)是一個(gè)包含模板相關(guān)屬性的數(shù)據(jù)對象 “可選” return createElement('div', { 'class': { foo: true, bar: false }, style: { color: 'red', fontSize: '14px' }, attrs: { id: 'boo' }, domProps: { innerHTML: 'Hello Vue!' } }) }})let app = new Vue({ el: '#app'})
最終生成的DOM,將會(huì)帶一些屬性和內(nèi)容的div
元素,如下圖所示:
createElement
還有第三個(gè)參數(shù),這個(gè)參數(shù)是可選的,可以給其傳一個(gè)String
或Array
。比如下面這個(gè)小示例:
<div id="app"> <custom-element></custom-element></div>Vue.component('custom-element', { render: function (createElement) { var self = this return createElement( 'div', // 第一個(gè)參數(shù)是一個(gè)簡單的HTML標(biāo)簽字符 “必選” { class: { title: true }, style: { border: '1px solid', padding: '10px' } }, // 第二個(gè)參數(shù)是一個(gè)包含模板相關(guān)屬性的數(shù)據(jù)對象 “可選” [ createElement('h1', 'Hello Vue!'), createElement('p', '開始學(xué)習(xí)Vue!') ] // 第三個(gè)參數(shù)是傳了多個(gè)子元素的一個(gè)數(shù)組 “可選” ) }})let app = new Vue({ el: '#app'})
最終的效果如下:
其實(shí)從上面這幾個(gè)小例來看,不難發(fā)現(xiàn),以往我們使用Vue.component()
創(chuàng)建組件的方式,都可以用render
函數(shù)配合createElement
來完成。你也會(huì)發(fā)現(xiàn),使用Vue.component()
和render
各有所長,正如文章開頭的一個(gè)示例代碼,就不適合Vue.component()
的template
,而使用render
更方便。
接下來看一個(gè)小示例,看看template
和render
方式怎么創(chuàng)建相同效果的一個(gè)組件:
<div id="app"> <custom-element></custom-element></div>Vue.component('custom-element', { template: `<div id="box" :class="{show: show}" @click="handleClick">Hello Vue!</div>`, data () { return { show: true } }, methods: { handleClick: function () { console.log('Clicked!') } }})
上面Vue.component()
中的代碼換成render
函數(shù)之后,可以這樣寫:
Vue.component('custom-element', { render: function (createElement) { return createElement('div', { class: { show: this.show }, attrs: { id: 'box' }, on: { click: this.handleClick } }, 'Hello Vue!') }, data () { return { show: true } }, methods: { handleClick: function () { console.log('Clicked!') } }})
最后聲明一個(gè)Vue實(shí)例,并掛載到id
為#app
的一個(gè)元素上:
let app = new Vue({ el: '#app'})
createElement
解析過程簡單的來看一下createElement
解析的過程,這部分需要對JS有一些功底。不然看起來有點(diǎn)蛋疼:
const SIMPLE_NORMALIZE = 1const ALWAYS_NORMALIZE = 2function createElement (context, tag, data, children, normalizationType, alwaysNormalize) { // 兼容不傳data的情況 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } // 如果alwaysNormalize是true // 那么normalizationType應(yīng)該設(shè)置為常量ALWAYS_NORMALIZE的值 if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE // 調(diào)用_createElement創(chuàng)建虛擬節(jié)點(diǎn) return _createElement(context, tag, data, children, normalizationType) } function _createElement (context, tag, data, children, normalizationType) { /** * 如果存在data.__ob__,說明data是被Observer觀察的數(shù)據(jù) * 不能用作虛擬節(jié)點(diǎn)的data * 需要拋出警告,并返回一個(gè)空節(jié)點(diǎn) * * 被監(jiān)控的data不能被用作vnode渲染的數(shù)據(jù)的原因是: * data在vnode渲染過程中可能會(huì)被改變,這樣會(huì)觸發(fā)監(jiān)控,導(dǎo)致不符合預(yù)期的操作 */ if (data && data.__ob__) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // 當(dāng)組件的is屬性被設(shè)置為一個(gè)falsy的值 // Vue將不會(huì)知道要把這個(gè)組件渲染成什么 // 所以渲染一個(gè)空節(jié)點(diǎn) if (!tag) { return createEmptyVNode() } // 作用域插槽 if (Array.isArray(children) && typeof children[0] === 'function') { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } // 根據(jù)normalizationType的值,選擇不同的處理方法 if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns // 如果標(biāo)簽名是字符串類型 if (typeof tag === 'string') { let Ctor // 獲取標(biāo)簽名的命名空間 ns = config.getTagNamespace(tag) // 判斷是否為保留標(biāo)簽 if (config.isReservedTag(tag)) { // 如果是保留標(biāo)簽,就創(chuàng)建一個(gè)這樣的vnode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) // 如果不是保留標(biāo)簽,那么我們將嘗試從vm的components上查找是否有這個(gè)標(biāo)簽的定義 } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) { // 如果找到了這個(gè)標(biāo)簽的定義,就以此創(chuàng)建虛擬組件節(jié)點(diǎn) vnode = createComponent(Ctor, data, context, children, tag) } else { // 兜底方案,正常創(chuàng)建一個(gè)vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } // 當(dāng)tag不是字符串的時(shí)候,我們認(rèn)為tag是組件的構(gòu)造類 // 所以直接創(chuàng)建 } else { vnode = createComponent(tag, data, context, children) } // 如果有vnode if (vnode) { // 如果有namespace,就應(yīng)用下namespace,然后返回vnode if (ns) applyNS(vnode, ns) return vnode // 否則,返回一個(gè)空節(jié)點(diǎn) } else { return createEmptyVNode() } }}
簡單的梳理了一個(gè)流程圖,可以參考下
這部分代碼和流程圖來自于@JoeRay61的《Vue原理解析之Virtual DOM》一文。
在使用Vue模板的時(shí)候,我們可以在模板中靈活的使用v-if
、v-for
、v-model
和<slot>
之類的。但在render
函數(shù)中是沒有提供專用的API。如果在render
使用這些,需要使用原生的JavaScript來實(shí)現(xiàn)。
v-if
和v-for
在render
函數(shù)中可以使用if/else
和map
來實(shí)現(xiàn)template
中的v-if
和v-for
。
<ul v-if="items.length"> <li v-for="item in items">{{ item }}</li></ul><p v-else>No items found.</p>
換成render
函數(shù),可以這樣寫:
Vue.component('item-list',{ props: ['items'], render: function (createElement) { if (this.items.length) { return createElement('ul', this.items.map((item) => { return createElement('item') })) } else { return createElement('p', 'No items found.') } }})<div id="app"> <item-list :items="items"></item-list></div>let app = new Vue({ el: '#app', data () { return { items: ['大漠', 'W3cplus', 'blog'] } }})
得到的效果如下:
v-model
render
函數(shù)中也沒有與v-model
相應(yīng)的API,如果要實(shí)現(xiàn)v-model
類似的功能,同樣需要使用原生JavaScript來實(shí)現(xiàn)。
<div id="app"> <el-input :name="name" @input="val => name = val"></el-input></div>Vue.component('el-input', { render: function (createElement) { var self = this return createElement('input', { domProps: { value: self.name }, on: { input: function (event) { self.$emit('input', event.target.value) } } }) }, props: { name: String }})let app = new Vue({ el: '#app', data () { return { name: '大漠' } }})
刷新你的瀏覽器,可以看到效果如下:
這就是深入底層要付出的,盡管麻煩了一些,但相對于 v-model
來說,你可以更靈活地控制。
你可以從this.$slots
獲取VNodes列表中的靜態(tài)內(nèi)容:
render: function (createElement) { // 相當(dāng)于 `<div><slot></slot></div>` return createElement('div', this.$slots.default)}
還可以從this.$scopedSlots
中獲得能用作函數(shù)的作用域插槽,這個(gè)函數(shù)返回VNodes:
props: ['message'],render: function (createElement) { // `<div><slot :text="message"></slot></div>` return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ])}
如果要用渲染函數(shù)向子組件中傳遞作用域插槽,可以利用VNode數(shù)據(jù)中的scopedSlots
域:
<div id="app"> <custom-ele></custom-ele></div>Vue.component('custom-ele', { render: function (createElement) { return createElement('div', [ createElement('child', { scopedSlots: { default: function (props) { return [ createElement('span', 'From Parent Component'), createElement('span', props.text) ] } } }) ]) }})Vue.component('child', { render: function (createElement) { return createElement('strong', this.$scopedSlots.default({ text: 'This is Child Component' })) }})let app = new Vue({ el: '#app'})
如果寫習(xí)慣了template
,然后要用render
函數(shù)來寫,一定會(huì)感覺好痛苦,特別是面對復(fù)雜的組件的時(shí)候。不過我們在Vue中使用JSX可以讓我們回到更接近于模板的語法上。
import AnchoredHeading from './AnchoredHeading.vue'new Vue({ el: '#demo', render: function (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) }})
將
h
作為createElement
的別名是 Vue 生態(tài)系統(tǒng)中的一個(gè)通用慣例,實(shí)際上也是 JSX 所要求的,如果在作用域中h
失去作用,在應(yīng)用中會(huì)觸發(fā)報(bào)錯(cuò)。
回過頭來看,Vue中的渲染核心關(guān)鍵的幾步流程還是非常清晰的:
new Vue
,執(zhí)行初始化$mount
方法,通過自定義render
方法、template
、el
等生成render
函數(shù)Watcher
監(jiān)聽數(shù)據(jù)的變化render
函數(shù)執(zhí)行生成VNode對象patch
方法,對比新舊VNode對象,通過DOM Diff算法,添加、修改、刪除真正的DOM元素至此,整個(gè)new Vue
的渲染過程完畢。
而這篇文章,主要把精力集中在render
函數(shù)這一部分。學(xué)習(xí)了怎么用render
函數(shù)來創(chuàng)建組件,以及了解了其中createElement
。
最后要說的是,上文雖然以學(xué)習(xí)render
函數(shù),但文中涉及了Vue不少的知識點(diǎn),也有點(diǎn)零亂。初學(xué)者自己根據(jù)自己獲取所要的知識點(diǎn)。由于本人也是初涉Vue相關(guān)的知識點(diǎn),如果文章中有不對之處,煩請路過的大神拍正。