亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術(shù)文章
文章詳情頁

詳解vue 組件的實現(xiàn)原理

瀏覽:3日期:2022-10-31 16:26:30

組件機制的設(shè)計,可以讓開發(fā)者把一個復(fù)雜的應(yīng)用分割成一個個功能獨立組件,降低開發(fā)的難度的同時,也提供了極好的復(fù)用性和可維護性。本文我們一起從源碼的角度,了解一下組件的底層實現(xiàn)原理。

組件注冊時做了什么?

在Vue中使用組件,要做的第一步就是注冊。Vue提供了全局注冊和局部注冊兩種方式。

全局注冊方式如下:

Vue.component(’my-component-name’, { /* ... */ })

局部注冊方式如下:

var ComponentA = { /* ... */ }new Vue({ el: ’#app’, components: { ’component-a’: ComponentA }})

全局注冊的組件,會在任何Vue實例中使用。局部注冊的組件,只能在該組件的注冊地,也就是注冊該組件的Vue實例中使用,甚至Vue實例的子組件中也不能使用。

有一定Vue使用經(jīng)驗的小伙伴都了解上面的差異,但是為啥會有這樣的差異呢?我們從組件注冊的代碼實現(xiàn)上進行解釋。

// Vue.component的核心代碼// ASSET_TYPES = [’component’, ’directive’, ’filter’]ASSET_TYPES.forEach(type => { Vue[type] = function (id, definition ){ if (!definition) { return this.options[type + ’s’][id] } else { // 組件注冊 if (type === ’component’ && isPlainObject(definition)) { definition.name = definition.name || id // 如果definition是一個對象,需要調(diào)用Vue.extend()轉(zhuǎn)換成函數(shù)。Vue.extend會創(chuàng)建一個Vue的子類(組件類),并返回子類的構(gòu)造函數(shù)。 definition = this.options._base.extend(definition) }// ...省略其他代碼 // 這里很關(guān)鍵,將組件添加到構(gòu)造函數(shù)的選項對象中Vue.options上。 this.options[type + ’s’][id] = definition return definition } } })

// Vue的構(gòu)造函數(shù)function Vue(options){ if (process.env.NODE_ENV !== ’production’ && !(this instanceof Vue) ) { warn(’Vue is a constructor and should be called with the `new` keyword’) } this._init(options) }// Vue的初始化中進行選項對象的合并Vue.prototype._init = function (options) { const vm = this vm._uid = uid++ vm._isVue = true // ...省略其他代碼 if (options && options._isComponent) { initInternalComponent(vm, options) } else { // 合并vue選項對象,合并構(gòu)造函數(shù)的選項對象和實例中的選項對象 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // ...省略其他代碼 }

以上摘取了組件注冊的主要代碼。可以看到Vue實例的選項對象由Vue的構(gòu)造函數(shù)選項對象和Vue實例的選項對象兩部分組成。

全局注冊的組件,實際上通過Vue.component添加到了Vue構(gòu)造函數(shù)的選項對象 Vue.options.components 上了。

Vue 在實例化時(new Vue(options))所指定的選項對象會與構(gòu)造函數(shù)的選項對象合并作為Vue實例最終的選項對象。因此,全局注冊的組件在所有的Vue實例中都可以使用,而在Vue實例中局部注冊的組件只會影響Vue實例本身。

為啥在HTML模板中可以正常使用組件標(biāo)簽?

我們知道組件可以跟普通的HTML一樣在模板中直接使用。例如:

<div id='app'> <!--使用組件button-counter--> <button-counter></button-counter></div>

// 全局注冊一個名為 button-counter 的組件Vue.component(’button-counter’, { data: function () { return { count: 0 } }, template: ’<button v-on:click='count++'>You clicked me {{ count }} times.</button>’})// 創(chuàng)建Vue實例new Vue({ el: ’#app’})

那么,當(dāng)Vue解析到自定義的組件標(biāo)簽時是如何處理的呢?

Vue 對組件標(biāo)簽的解析與普通HTML標(biāo)簽的解析一樣,不會因為是非 HTML標(biāo)準(zhǔn)的標(biāo)簽而特殊處理。處理過程中第一個不同的地方出現(xiàn)在vnode節(jié)點創(chuàng)建時。vue 內(nèi)部通過_createElement函數(shù)實現(xiàn)vnode的創(chuàng)建。

export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number): VNode | Array<VNode> { //...省略其他代碼 let vnode, ns if (typeof tag === ’string’) { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) // 如果是普通的HTML標(biāo)簽 if (config.isReservedTag(tag)) { vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, ’components’, tag))) { // 如果是組件標(biāo)簽,e.g. my-custom-tag vnode = createComponent(Ctor, data, context, children, tag) } else { vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // direct component options / constructor vnode = createComponent(tag, data, context, children) } if (Array.isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() }}

以文中的button-counter組件為例,由于button-counter標(biāo)簽不是合法的HTML標(biāo)簽,不能直接new VNode()創(chuàng)建vnode。Vue 會通過resolveAsset函數(shù)檢查該標(biāo)簽是否為自定義組件的標(biāo)簽。

export function resolveAsset ( options: Object, type: string, id: string, warnMissing?: boolean): any { /* istanbul ignore if */ if (typeof id !== ’string’) { return } const assets = options[type] // 首先檢查vue實例本身有無該組件 if (hasOwn(assets, id)) return assets[id] const camelizedId = camelize(id) if (hasOwn(assets, camelizedId)) return assets[camelizedId] const PascalCaseId = capitalize(camelizedId) if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId] // 如果實例上沒有找到,去查找原型鏈 const res = assets[id] || assets[camelizedId] || assets[PascalCaseId] if (process.env.NODE_ENV !== ’production’ && warnMissing && !res) { warn( ’Failed to resolve ’ + type.slice(0, -1) + ’: ’ + id, options ) } return res}

button-counter是我們?nèi)肿缘慕M件,顯然可以在this.$options.components找到其定義。因此,Vue會執(zhí)行createComponent函數(shù)來生成組件的vnode。

// createComponentexport function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void { if (isUndef(Ctor)) { return } // 獲取Vue的構(gòu)造函數(shù) const baseCtor = context.$options._base // 如果Ctor是一個選項對象,需要使用Vue.extend使用選項對象,創(chuàng)建將組件選項對象轉(zhuǎn)換成一個Vue的子類 if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } // 如果Ctor還不是一個構(gòu)造函數(shù)或者異步組件工廠函數(shù),不再往下執(zhí)行。 if (typeof Ctor !== ’function’) { if (process.env.NODE_ENV !== ’production’) { warn(`Invalid Component definition: ${String(Ctor)}`, context) } return } // 異步組件 let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor) if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } data = data || {} // 重新解析構(gòu)造函數(shù)的選項對象,在組件構(gòu)造函數(shù)創(chuàng)建后,Vue可能會使用全局混入造成構(gòu)造函數(shù)選項對象改變。 resolveConstructorOptions(Ctor) // 處理組件的v-model if (isDef(data.model)) { transformModel(Ctor.options, data) } // 提取props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // 函數(shù)式組件 if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } const listeners = data.on data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { const slot = data.slot data = {} if (slot) { data.slot = slot } } // 安裝組件hooks installComponentHooks(data) // 創(chuàng)建 vnode const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ’’}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) return vnode}

由于Vue允許通過一個選項對象定義組件,Vue需要使用Vue.extend將組件的選項對象轉(zhuǎn)換成一個構(gòu)造函數(shù)。

/** * Vue類繼承,以Vue的原型為原型創(chuàng)建Vue組件子類。繼承實現(xiàn)方式是采用Object.create(),在內(nèi)部實現(xiàn)中,加入了緩存的機制,避免重復(fù)創(chuàng)建子類。 */ Vue.extend = function (extendOptions: Object): Function { // extendOptions 是組件的選項對象,與vue所接收的一樣 extendOptions = extendOptions || {} // Super變量保存對父類Vue的引用 const Super = this // SuperId 保存父類的cid const SuperId = Super.cid // 緩存構(gòu)造函數(shù) const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } // 獲取組件的名字 const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== ’production’ && name) { validateComponentName(name) } // 定義組件的構(gòu)造函數(shù) const Sub = function VueComponent (options) { this._init(options) } // 組件的原型對象指向Vue的選項對象 Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub // 為組件分配一個cid Sub.cid = cid++ // 將組件的選項對象與Vue的選項合并 Sub.options = mergeOptions( Super.options, extendOptions ) // 通過super屬性指向父類 Sub[’super’] = Super // 將組件實例的props和computed屬代理到組件原型對象上,避免每個實例創(chuàng)建的時候重復(fù)調(diào)用Object.defineProperty。 if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // 復(fù)制父類Vue上的extend/mixin/use等全局方法 Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // 復(fù)制父類Vue上的component、directive、filter等資源注冊方法 ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // 保存父類Vue的選項對象 Sub.superOptions = Super.options // 保存組件的選項對象 Sub.extendOptions = extendOptions // 保存最終的選項對象 Sub.sealedOptions = extend({}, Sub.options) // 緩存組件的構(gòu)造函數(shù) cachedCtors[SuperId] = Sub return Sub }}

還有一處重要的代碼是installComponentHooks(data)。該方法會給組件vnode的data添加組件鉤子,這些鉤子在組件的不同階段被調(diào)用,例如init鉤子在組件patch時會調(diào)用。

function installComponentHooks (data: VNodeData) { const hooks = data.hook || (data.hook = {}) for (let i = 0; i < hooksToMerge.length; i++) { const key = hooksToMerge[i] // 外部定義的鉤子 const existing = hooks[key] // 內(nèi)置的組件vnode鉤子 const toMerge = componentVNodeHooks[key] // 合并鉤子 if (existing !== toMerge && !(existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } }}// 組件vnode的鉤子。const componentVNodeHooks = { // 實例化組件 init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { // 生成組件實例 const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) // 掛載組件,與vue的$mount一樣 child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }, prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { const options = vnode.componentOptions const child = vnode.componentInstance = oldVnode.componentInstance updateChildComponent( child, options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ) }, insert (vnode: MountedComponentVNode) { const { context, componentInstance } = vnode if (!componentInstance._isMounted) { componentInstance._isMounted = true // 觸發(fā)組件的mounted鉤子 callHook(componentInstance, ’mounted’) } if (vnode.data.keepAlive) { if (context._isMounted) { queueActivatedComponent(componentInstance) } else { activateChildComponent(componentInstance, true /* direct */) } } }, destroy (vnode: MountedComponentVNode) { const { componentInstance } = vnode if (!componentInstance._isDestroyed) { if (!vnode.data.keepAlive) { componentInstance.$destroy() } else { deactivateChildComponent(componentInstance, true /* direct */) } } }}const hooksToMerge = Object.keys(componentVNodeHooks)

最后,與普通HTML標(biāo)簽一樣,為組件生成vnode節(jié)點:

// 創(chuàng)建 vnode const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ’’}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory )

組件在patch時對vnode的處理與普通標(biāo)簽有所不同。

Vue 如果發(fā)現(xiàn)正在patch的vnode是組件,那么調(diào)用createComponent方法。

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { const isReactivated = isDef(vnode.componentInstance) && i.keepAlive // 執(zhí)行組件鉤子中的init鉤子,創(chuàng)建組件實例 if (isDef(i = i.hook) && isDef(i = i.init)) { i(vnode, false /* hydrating */) } // init鉤子執(zhí)行后,如果vnode是個子組件,該組件應(yīng)該創(chuàng)建一個vue子實例,并掛載到DOM元素上。子組件的vnode.elm也設(shè)置完成。然后我們只需要返回該DOM元素。 if (isDef(vnode.componentInstance)) { // 設(shè)置vnode.elm initComponent(vnode, insertedVnodeQueue) // 將組件的elm插入到父組件的dom節(jié)點上 insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) { reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }

createComponent會調(diào)用組件vnode的data對象上定義的init鉤子方法,創(chuàng)建組件實例。現(xiàn)在我們回過頭來看下init鉤子的代碼:

// ... 省略其他代碼 init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { // 生成組件實例 const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) // 掛載組件,與vue的$mount一樣 child.$mount(hydrating ? vnode.elm : undefined, hydrating) } } // ...省略其他代碼

由于組件是初次創(chuàng)建,因此init鉤子會調(diào)用createComponentInstanceForVnode創(chuàng)建一個組件實例,并賦值給vnode.componentInstance。

export function createComponentInstanceForVnode ( vnode: any, parent: any,): Component { // 內(nèi)部組件選項 const options: InternalComponentOptions = { // 標(biāo)記是否是組件 _isComponent: true, // 父Vnode _parentVnode: vnode, // 父Vue實例 parent } // check inline-template render functions const inlineTemplate = vnode.data.inlineTemplate if (isDef(inlineTemplate)) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } // new 一個組件實例。組件實例化 與 new Vue() 執(zhí)行的過程相同。 return new vnode.componentOptions.Ctor(options)}

createComponentInstanceForVnode 中會執(zhí)行 new vnode.componentOptions.Ctor(options)。由前面我們在創(chuàng)建組件vnode時可知,vnode.componentOptions的值是一個對象:{ Ctor, propsData, listeners, tag, children },其中包含了組件的構(gòu)造函數(shù)Ctor。因此 new vnode.componentOptions.Ctor(options)等價于new VueComponent(options)。

// 生成組件實例const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)// 掛載組件,與vue的$mount一樣child.$mount(hydrating ? vnode.elm : undefined, hydrating)

等價于:

new VueComponent(options).$mount(hydrating ? vnode.elm : undefined, hydrating)

這段代碼想必大家都很熟悉了,是組件初始化和掛載的過程。組件的初始化和掛載與在前文中所介紹Vue初始化和掛載過程相同,因此不再展開說明。大致的過程就是創(chuàng)建了一個組件實例并掛載后。使用initComponent將組件實例的$el設(shè)置為vnode.elm的值。最后,調(diào)用insert將組件實例的DOM根節(jié)點插入其父節(jié)點。然后就完成了組件的處理。

總結(jié)

通過對組件底層實現(xiàn)的分析,我們可以知道,每個組件都是一個VueComponent實例,而VueComponent又是繼承自Vue。每個組件實例獨立維護自己的狀態(tài)、模板的解析、DOM的創(chuàng)建和更新。篇幅有限,文中只分析了基本的組件的注冊解析過程,未對異步組件、keep-alive等做分析。等后面再慢慢補上。

以上就是詳解vue 組件的實現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于vue組件的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Vue
相關(guān)文章:
主站蜘蛛池模板: 亚洲日韩欧美制服二区dvd | 中文字幕第99页 | 久久免费视频在线观看30 | 日韩欧美亚洲一区 | 日韩一级黄色影片 | 久久久国产99久久国产首页 | 香港黄页亚洲一级 | 91精品国产91久久久久久最新 | 老妇综合久久香蕉蜜桃 | 精品一区二区高清在线观看 | 国产91在线看| 国产美女亚洲精品久久久久久 | 亚洲精品成人一区二区www | 黄色一级毛片免费看 | 黄色片一级| 日韩欧美视频在线播放 | 青青青视频自偷自拍视频1 青青青视频免费一区二区 青青青视频蜜桃一区二区 青青青爽国产在线视频 | 国产午夜精品久久久久免费视 | 欧美精品一区二区三区在线 | 看一级黄色 | 亚洲20p| 毛片在线看网站 | 亚洲精品午夜久久久伊人 | 12306影院午夜入口 | 中国国产一国产一级毛片视频 | 亚洲丝袜第一页 | 国产免费网站看v片元遮挡 国产免费自拍 | 亚洲国产精品区 | 亚洲黄页 | 国产成人涩涩涩视频在线观看免费 | 亚洲欧美日韩一区 | 一级毛片aa高清免费观看 | 国产伦理播放一区二区 | 成人午夜免费视频 | 色免费看| 91亚洲免费视频 | 金发欧美一区在线观看 | 亚洲一区二区在线免费观看 | 一区二区在线播放福利视频 | 亚洲一级片在线观看 | 91国在线啪精品一区 |