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

您的位置:首頁技術文章
文章詳情頁

詳解vue 組件的實現原理

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

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

組件注冊時做了什么?

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

全局注冊方式如下:

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

局部注冊方式如下:

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

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

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

// 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是一個對象,需要調用Vue.extend()轉換成函數。Vue.extend會創建一個Vue的子類(組件類),并返回子類的構造函數。 definition = this.options._base.extend(definition) }// ...省略其他代碼 // 這里很關鍵,將組件添加到構造函數的選項對象中Vue.options上。 this.options[type + ’s’][id] = definition return definition } } })

// Vue的構造函數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選項對象,合并構造函數的選項對象和實例中的選項對象 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // ...省略其他代碼 }

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

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

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

為啥在HTML模板中可以正常使用組件標簽?

我們知道組件可以跟普通的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>’})// 創建Vue實例new Vue({ el: ’#app’})

那么,當Vue解析到自定義的組件標簽時是如何處理的呢?

Vue 對組件標簽的解析與普通HTML標簽的解析一樣,不會因為是非 HTML標準的標簽而特殊處理。處理過程中第一個不同的地方出現在vnode節點創建時。vue 內部通過_createElement函數實現vnode的創建。

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標簽 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))) { // 如果是組件標簽,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標簽不是合法的HTML標簽,不能直接new VNode()創建vnode。Vue 會通過resolveAsset函數檢查該標簽是否為自定義組件的標簽。

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是我們全局注冊的組件,顯然可以在this.$options.components找到其定義。因此,Vue會執行createComponent函數來生成組件的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的構造函數 const baseCtor = context.$options._base // 如果Ctor是一個選項對象,需要使用Vue.extend使用選項對象,創建將組件選項對象轉換成一個Vue的子類 if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } // 如果Ctor還不是一個構造函數或者異步組件工廠函數,不再往下執行。 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 || {} // 重新解析構造函數的選項對象,在組件構造函數創建后,Vue可能會使用全局混入造成構造函數選項對象改變。 resolveConstructorOptions(Ctor) // 處理組件的v-model if (isDef(data.model)) { transformModel(Ctor.options, data) } // 提取props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // 函數式組件 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) // 創建 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將組件的選項對象轉換成一個構造函數。

/** * Vue類繼承,以Vue的原型為原型創建Vue組件子類。繼承實現方式是采用Object.create(),在內部實現中,加入了緩存的機制,避免重復創建子類。 */ Vue.extend = function (extendOptions: Object): Function { // extendOptions 是組件的選項對象,與vue所接收的一樣 extendOptions = extendOptions || {} // Super變量保存對父類Vue的引用 const Super = this // SuperId 保存父類的cid const SuperId = Super.cid // 緩存構造函數 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) } // 定義組件的構造函數 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屬代理到組件原型對象上,避免每個實例創建的時候重復調用Object.defineProperty。 if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // 復制父類Vue上的extend/mixin/use等全局方法 Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // 復制父類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) // 緩存組件的構造函數 cachedCtors[SuperId] = Sub return Sub }}

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

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] // 內置的組件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 // 觸發組件的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標簽一樣,為組件生成vnode節點:

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

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

Vue 如果發現正在patch的vnode是組件,那么調用createComponent方法。

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

createComponent會調用組件vnode的data對象上定義的init鉤子方法,創建組件實例。現在我們回過頭來看下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) } } // ...省略其他代碼

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

export function createComponentInstanceForVnode ( vnode: any, parent: any,): Component { // 內部組件選項 const options: InternalComponentOptions = { // 標記是否是組件 _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() 執行的過程相同。 return new vnode.componentOptions.Ctor(options)}

createComponentInstanceForVnode 中會執行 new vnode.componentOptions.Ctor(options)。由前面我們在創建組件vnode時可知,vnode.componentOptions的值是一個對象:{ Ctor, propsData, listeners, tag, children },其中包含了組件的構造函數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初始化和掛載過程相同,因此不再展開說明。大致的過程就是創建了一個組件實例并掛載后。使用initComponent將組件實例的$el設置為vnode.elm的值。最后,調用insert將組件實例的DOM根節點插入其父節點。然后就完成了組件的處理。

總結

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

以上就是詳解vue 組件的實現原理的詳細內容,更多關于vue組件的資料請關注好吧啦網其它相關文章!

標簽: Vue
相關文章:
主站蜘蛛池模板: 国产欧美一区二区三区在线看 | 直接看毛片 | 亚洲精品国产一区二区三 | 国产一级做a爰片在线 | 正在播放国产巨作 | 1024在线看片 | 免费一级特黄 欧美大片 | 欧美日韩一二区 | 国内精品久久久久香蕉 | 黄毛片视频 | 国语对白刺激做受xxxxx在线 | 国产欧美日韩在线人成aaaa | 国产手机在线αⅴ片无码观看 | 国产大片中文字幕在线观看 | 国产精品嫩草研究院成人 | 我要看一级毛片 | 久久久久在线 | 正在播放淫亚洲 | 成人欧美一区二区三区在线观看 | 欧美一区永久视频免费观看 | 成年免费大片黄在线观看看 | 欧美一区综合 | 国产网红主播精品福利大秀专区 | 2022久久免费精品国产72精品 | 国产我不卡 | 亚洲综合激情六月婷婷在线观看 | 玖玖色视频| 国产午夜精品片一区二区三区 | 国产小视频在线观看 | 久久精品免看国产 | 成人免费大片黄在线观看com | 国产上床视频 | 亚洲精品欧美精品 | 小明看看在线 | 久久精品成人免费网站 | 91久久香蕉青青草原娱乐 | 精品视自拍视频在线观看 | 免费黄色大片视频 | 尤物视频网在线观看 | 高清精品美女在线播放 | 成年人的黄色 |