摘要:定義一個(gè)組件如下打印如下再回過頭看,可以發(fā)現(xiàn)他做的工作就是擴(kuò)展一個(gè)構(gòu)造函數(shù),并將這個(gè)構(gòu)造函數(shù)添加到現(xiàn)在我們已經(jīng)可以回答最開始的問題的組件是什么的組件其實(shí)就是擴(kuò)展的構(gòu)造函數(shù),并且在適當(dāng)?shù)臅r(shí)候?qū)嵗癁閷?shí)例。
[email protected]源碼學(xué)習(xí)---組件究竟是什么
本篇文章從最簡(jiǎn)單的情況入手,不考慮prop和組件間通信。
Vue.componentvue文檔告訴我們可以使用Vue.component(tagName, options)注冊(cè)一個(gè)組件
Vue.component("my-component", { // 選項(xiàng) })
毫無疑問這是一個(gè)全局API,我們順著代碼最終可以找到Vue.component是這樣的
Vue.component = function(id, definition) { definition.name = definition.name || id definition = Vue.extend(definition) this.options[type + "s"][id] = definition return definition }
Vue.component實(shí)際上是Vue.extend的封裝,Vue.extend如下:
Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const isFirstExtend = Super.cid === 0 if (isFirstExtend && extendOptions._Ctor) { return extendOptions._Ctor } let name = extendOptions.name || Super.options.name const Sub = function VueComponent (options) { this._init(options) } Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( Super.options, extendOptions ) Sub["super"] = Super // allow further extension Sub.extend = Super.extend // create asset registers, so extended classes // can have their private assets too. config._assetTypes.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // keep a reference to the super options at extension time. // later at instantiation we can check if Super"s options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions // cache constructor if (isFirstExtend) { extendOptions._Ctor = Sub } return Sub }
可以看到Vue.extend返回的實(shí)際上是一個(gè)構(gòu)造函數(shù)Sub,并且此構(gòu)造函數(shù)繼承自Vue。里面有這么幾行代碼
Sub.options = mergeOptions( Super.options, extendOptions )
那么Super.options(即Vue.options)是什么呢?
Vue.options = Object.create(null) // 包含components directives filters config._assetTypes.forEach(type => { Vue.options[type + "s"] = Object.create(null) }) util.extend(Vue.options.components, builtInComponents)
Vue.options事實(shí)上存放了系統(tǒng)以及用戶定義的component、directive、filter,builtInComponents為Vue內(nèi)置的組件(如keep-alive),打印看下:
所以Sub構(gòu)造函數(shù)的options不僅包含components、directives、filters,還包含預(yù)先定義的實(shí)例化時(shí)所需的選項(xiàng)。定義一個(gè)組件如下:
let MyComponent = Vue.extend({ data() { return { msg: "this"s compoennt" } }, render(h) { return h("p", this.msg) } })
打印MyComponent.options如下:
再回過頭看Vue.component,可以發(fā)現(xiàn)他做的工作就是擴(kuò)展一個(gè)Vue構(gòu)造函數(shù)(VueComponent),并將這個(gè)構(gòu)造函數(shù)(VueComponent)添加到Vue.options.components
現(xiàn)在我們已經(jīng)可以回答最開始的問題---vue的組件是什么?vue的組件其實(shí)就是擴(kuò)展的Vue構(gòu)造函數(shù),并且在適當(dāng)?shù)臅r(shí)候?qū)嵗癁閂ue實(shí)例。
組件對(duì)應(yīng)的vnode組件對(duì)應(yīng)的vnode是什么樣子?從一個(gè)簡(jiǎn)單的例子入手:
let MyComponent = Vue.component("my-component", { data() { return { msg: "this"s component" } }, render(h) { return h("p", this.msg) } }) window.app = new Vue({ render(h) { return h("my-component") } }).$mount("#root")
上篇文章已經(jīng)說道在initRender的時(shí)候會(huì)初始一個(gè)系統(tǒng)watcher,如下:
vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, noop)
上篇文章提到vm._render()返回的是一個(gè)虛擬dom(vnode),具體到本篇,那么組件標(biāo)簽會(huì)被解析成什么樣的虛擬節(jié)點(diǎn)呢?
事實(shí)上render的時(shí)候會(huì)首先調(diào)用createElement,根據(jù)傳入的tag(html標(biāo)簽或者組件標(biāo)簽)不同,vnode可以分為以下兩種:
platform built-in elements
這種就是普通的html標(biāo)簽(p、div、span等)對(duì)應(yīng)的vnode
component
當(dāng)tag是組件標(biāo)簽的時(shí)候,會(huì)調(diào)用createComponent,如下:
else if ((Ctor = resolveAsset(context.$options, "components", tag))) { // component return createComponent(Ctor, data, context, children, tag) }
這里的Ctor就是我們擴(kuò)展的組件構(gòu)造函數(shù),createComponent最終返回的vnode如下:
const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ""}`, data, undefined, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children } )
需要注意的是data有一個(gè)操作:
// merge component management hooks onto the placeholder node mergeHooks(data)
merge之后data.hook會(huì)添加四個(gè)方法:
init 實(shí)例化組件時(shí)調(diào)用
prepatch patch之前調(diào)用
insert 真實(shí)節(jié)點(diǎn)插入時(shí)調(diào)用
destory 組件實(shí)例銷毀時(shí)調(diào)用
實(shí)例化組件前文看到組件構(gòu)造函數(shù)實(shí)際上是存在組件對(duì)應(yīng)vnode的componentOptions中,那么究竟是什么時(shí)候?qū)嵗M件呢?
順著vm._update(vm._render(), hydrating)往下看發(fā)現(xiàn)最終調(diào)用的是patch操作,而對(duì)于組件實(shí)例化而言并不存在與之對(duì)應(yīng)的oldVnode(因?yàn)閛ldVnode是在組件更新后產(chǎn)生的),所以最終的邏輯歸到根據(jù)組件對(duì)應(yīng)的vnode創(chuàng)建真實(shí)dom節(jié)點(diǎn),即
createElm(vnode, insertedVnodeQueue)
我們還記得組件的構(gòu)造函數(shù)是vnode.componentOptions.Ctor,其實(shí)最終調(diào)用的也是這個(gè)構(gòu)造函數(shù)。
createElm函數(shù)中與組件初始化相關(guān)的關(guān)鍵代碼如下:
const data = vnode.data if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode) if (isDef(i = vnode.child)) { initComponent(vnode, insertedVnodeQueue) return vnode.elm } }
init的代碼如下:
function init (vnode: VNodeWithData, hydrating: boolean) { if (!vnode.child || vnode.child._isDestroyed) { const child = vnode.child = createComponentInstanceForVnode(vnode, activeInstance) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } } export function createComponentInstanceForVnode ( vnode: any, // we know it"s MountedComponentVNode but flow doesn"t parent: any // activeInstance in lifecycle state ): Component { const vnodeComponentOptions = vnode.componentOptions const options: InternalComponentOptions = { _isComponent: true, parent, propsData: vnodeComponentOptions.propsData, _componentTag: vnodeComponentOptions.tag, _parentVnode: vnode, _parentListeners: vnodeComponentOptions.listeners, _renderChildren: vnodeComponentOptions.children } // check inline-template render functions const inlineTemplate = vnode.data.inlineTemplate if (inlineTemplate) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } return new vnodeComponentOptions.Ctor(options) }
經(jīng)過init之后可以看到組件vnode.child對(duì)應(yīng)的就是組件的實(shí)例,且child.$el即為組件對(duì)應(yīng)的真實(shí)dom,但是實(shí)際上createElm返回的是vnode.elm,怎么回事?事實(shí)上initComponent
中做了處理
vnode.elm = vnode.child.$el
綜上,組件實(shí)例化是在由虛擬dom映射為真實(shí)dom時(shí)完成的。
寫到這里已經(jīng)對(duì)組件機(jī)制有了初步的認(rèn)識(shí),數(shù)據(jù)的傳遞、父子組件通信本文并沒有涉及,留到以后再看。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90375.html
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:借著產(chǎn)品層面的功能和視覺升級(jí),我們用對(duì)它進(jìn)行了一次技術(shù)重構(gòu)。前端優(yōu)化是一個(gè)讓人技術(shù)提升的,希望你也能從這里學(xué)到一些東西。年最流行的前端鏈接我們每周會(huì)給多名前端開發(fā)者發(fā)送新聞郵件。 面試 -- 網(wǎng)絡(luò) HTTP 現(xiàn)在面試門檻越來越高,很多開發(fā)者對(duì)于網(wǎng)絡(luò)知識(shí)這塊了解的不是很多,遇到這些面試題會(huì)手足無措。本篇文章知識(shí)主要集中在 HTTP 這塊。文中知識(shí)來自 《圖解 HTTP》與維基百科,若有錯(cuò)...
摘要:寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請(qǐng)點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號(hào)也可以吧原理白話版從模板上使用到掛載到頁(yè)面 寫文章不容易,點(diǎn)個(gè)贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...
摘要:前言這節(jié)凈是些嘮叨,只想看升級(jí)的可直接跳過。在不久之前,如約發(fā)布了版本。正如計(jì)劃之初,博客的版本也將升級(jí)到。升級(jí)之旅首先,升級(jí)依賴。那該怎么做哪再一次谷哥和查閱文檔,然而一無所獲。返回的是整個(gè)項(xiàng)目路由的實(shí)例,它是只讀的。 Troubleshooting of upgrading Vue from 1.0 to 2.0 系列文章: Vue 2.0 升(cai)級(jí)(keng)之旅 (本...
摘要:上周末看這篇文章時(shí),偶有靈光,所以,分享出來給大家一起看看前端面試四月二十家前端面試題分享請(qǐng)各位讀者添加一下作者的微信公眾號(hào),以后有新的文章,將在微信公眾號(hào)直接推送給各位,非常感謝。 前端切圖神器 avocode 有了這個(gè)神器,切圖再也腰不酸,腿不疼了。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,...
閱讀 3163·2021-11-22 14:45
閱讀 3312·2019-08-29 13:11
閱讀 2313·2019-08-29 12:31
閱讀 932·2019-08-29 11:21
閱讀 3000·2019-08-29 11:09
閱讀 3627·2019-08-28 18:11
閱讀 1432·2019-08-26 13:58
閱讀 1283·2019-08-26 13:27