日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Vue3.0進階之動態(tài)組件探秘

本文轉(zhuǎn)載自微信公眾號「全棧修仙之路」,作者全棧修仙之路。轉(zhuǎn)載本文請聯(lián)系全棧修仙之路公眾號。 

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信平臺小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了廣靈免費建站歡迎大家使用!

本文是 Vue 3.0 進階系列 的第四篇文章,在這篇文章中,阿寶哥將介紹 Vue 3 中的內(nèi)置組件 —— component,該組件的作用是渲染一個 “元組件” 為動態(tài)組件。如果你對動態(tài)組件還不了解的話也沒關(guān)系,文中阿寶哥會通過具體的示例,來介紹動態(tài)組件的應(yīng)用。

由于動態(tài)組件內(nèi)部與組件注冊之間有一定的聯(lián)系,所以為了讓大家能夠更好地了解動態(tài)組件的內(nèi)部原理,阿寶哥會先介紹組件注冊的相關(guān)知識。

一、組件注冊

1.1 全局注冊

在 Vue 3.0 中,通過使用 app 對象的 component 方法,可以很容易地注冊或檢索全局組件。component 方法支持兩個參數(shù):

  • name:組件名稱;
  • component:組件定義對象。

接下來,我們來看一個簡單的示例:

 
 
 
 
  1.  
  2.     
  3.     
  4.     
 
  •  
  • 在以上代碼中,我們通過 app.component 方法注冊了 3 個組件,這些組件都是全局注冊的 。也就是說它們在注冊之后可以用在任何新創(chuàng)建的組件實例的模板中。

    該示例的代碼比較簡單,主要包含 3 個步驟:創(chuàng)建 App 對象、注冊全局組件和應(yīng)用掛載。其中創(chuàng)建 App 對象的細節(jié),阿寶哥會在后續(xù)的文章中單獨介紹,下面我們將重點分析其他 2 個步驟,首先我們先來分析注冊全局組件的過程。

    1.2 注冊全局組件的過程

    在以上示例中,我們使用 app 對象的 component 方法來注冊全局組件:

     
     
     
     
    1. app.component('component-a', { 
    2.   template: "

      我是組件A

    3. }); 

    當然,除了注冊全局組件之外,我們也可以注冊局部組件,因為組件中也接受一個 components 的選項:

     
     
     
     
    1. const app = Vue.createApp({ 
    2.   components: { 
    3.     'component-a': ComponentA, 
    4.     'component-b': ComponentB 
    5.   } 
    6. }) 

    需要注意的是,局部注冊的組件在其子組件中是不可用的。接下來,我們來繼續(xù)介紹注冊全局組件的過程。對于前面的示例來說,我們使用的 app.component 方法被定義在 runtime-core/src/apiCreateApp.ts 文件中:

     
     
     
     
    1. export function createAppAPI
    2.   render: RootRenderFunction, 
    3.   hydrate?: RootHydrateFunction 
    4. ): CreateAppFunction { 
    5.   return function createApp(rootComponent, rootProps = null) { 
    6.     const context = createAppContext() 
    7.     const installedPlugins = new Set() 
    8.     let isMounted = false 
    9.  
    10.     const app: App = (context.app = { 
    11.       // 省略部分代碼 
    12.       _context: context, 
    13.  
    14.       // 注冊或檢索全局組件 
    15.       component(name: string, component?: Component): any { 
    16.         if (__DEV__) { 
    17.           validateComponentName(name, context.config) 
    18.         } 
    19.         if (!component) { // 獲取name對應(yīng)的組件 
    20.           return context.components[name] 
    21.         } 
    22.         if (__DEV__ && context.components[name]) { // 重復(fù)注冊提示 
    23.           warn(`Component "${name}" has already been registered in target app.`) 
    24.         } 
    25.         context.components[name] = component // 注冊全局組件 
    26.         return app 
    27.       }, 
    28.     }) 
    29.  
    30.     return app 
    31.   } 

    當所有的組件都注冊成功之后,它們會被保存到 context 對象的 components 屬性中,具體如下圖所示:

    顧名思義 context 是表示應(yīng)用的上下文對象,那么該對象是如何創(chuàng)建的呢?其實,該對象是通過 createAppContext 函數(shù)來創(chuàng)建的:

     
     
     
     
    1. const context = createAppContext() 

    而 createAppContext 函數(shù)被定義在 runtime-core/src/apiCreateApp.ts 文件中:

     
     
     
     
    1. // packages/runtime-core/src/apiCreateApp.ts 
    2. export function createAppContext(): AppContext { 
    3.   return { 
    4.     app: null as any, 
    5.     config: { // 應(yīng)用的配置對象 
    6.       isNativeTag: NO, 
    7.       performance: false, 
    8.       globalProperties: {}, 
    9.       optionMergeStrategies: {}, 
    10.       isCustomElement: NO, 
    11.       errorHandler: undefined, 
    12.       warnHandler: undefined 
    13.     }, 
    14.     mixins: [], // 保存應(yīng)用內(nèi)的混入 
    15.     components: {}, // 保存全局組件的信息 
    16.     directives: {}, // 保存全局指令的信息 
    17.     provides: Object.create(null) 
    18.   } 

    分析完 app.component 方法之后,是不是覺得組件注冊的過程還是挺簡單的。那么對于已注冊的組件,何時會被使用呢?要回答這個問題,我們就需要分析另一個步驟 —— 應(yīng)用掛載。

    1.3 應(yīng)用掛載的過程

    為了更加直觀地了解應(yīng)用掛載的過程,阿寶哥利用 Chrome 開發(fā)者工具的 Performance 標簽欄,記錄了應(yīng)用掛載的主要過程:

    在上圖中我們發(fā)現(xiàn)了一個與組件相關(guān)的函數(shù) resolveComponent。很明顯,該函數(shù)用于解析組件,且該函數(shù)在 render 方法中會被調(diào)用。在源碼中,我們找到了該函數(shù)的定義:

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. const COMPONENTS = 'components' 
    3.  
    4. export function resolveComponent(name: string): ConcreteComponent | string { 
    5.   return resolveAsset(COMPONENTS, name) || name 

    由以上代碼可知,在 resolveComponent 函數(shù)內(nèi)部,會繼續(xù)調(diào)用 resolveAsset 函數(shù)來執(zhí)行具體的解析操作。在分析 resolveAsset 函數(shù)的具體實現(xiàn)之前,我們在 resolveComponent 函數(shù)內(nèi)部加個斷點,來一睹 render 方法的 “芳容”:

    在上圖中,我們看到了解析組件的操作,比如 _resolveComponent("component-a")。前面我們已經(jīng)知道在 resolveComponent 函數(shù)內(nèi)部會繼續(xù)調(diào)用 resolveAsset 函數(shù),該函數(shù)的具體實現(xiàn)如下:

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. function resolveAsset( 
    3.   type: typeof COMPONENTS | typeof DIRECTIVES, 
    4.   name: string, 
    5.   warnMissing = true 
    6. ) { 
    7.   const instance = currentRenderingInstance || currentInstance 
    8.   if (instance) { 
    9.     const Component = instance.type 
    10.     // 省略大部分處理邏輯 
    11.     const res = 
    12.       // 局部注冊 
    13.       // check instance[type] first for components with mixin or extends. 
    14.       resolve(instance[type] || (Component as ComponentOptions)[type], name) || 
    15.       // 全局注冊 
    16.       resolve(instance.appContext[type], name) 
    17.     return res 
    18.   } else if (__DEV__) { 
    19.     warn( 
    20.       `resolve${capitalize(type.slice(0, -1))} ` + 
    21.         `can only be used in render() or setup().` 
    22.     ) 
    23.   } 

    因為注冊組件時,使用的是全局注冊的方式,所以解析的過程會執(zhí)行 resolve(instance.appContext[type], name) 該語句,其中 resolve 方法的定義如下:

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. function resolve(registry: Record | undefined, name: string) { 
    3.   return ( 
    4.     registry && 
    5.     (registry[name] || 
    6.       registry[camelize(name)] || 
    7.       registry[capitalize(camelize(name))]) 
    8.   ) 

    分析完以上的處理流程,我們在解析全局注冊的組件時,會通過 resolve 函數(shù)從應(yīng)用的上下文對象中獲取已注冊的組件對象。

     
     
     
     
    1. (function anonymous() { 
    2.     const _Vue = Vue 
    3.  
    4.     return function render(_ctx, _cache) { 
    5.         with (_ctx) { 
    6.           const {resolveComponent: _resolveComponent, createVNode: _createVNode,  
    7.             Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock} = _Vue 
    8.  
    9.             const _component_component_a = _resolveComponent("component-a") 
    10.             const _component_component_b = _resolveComponent("component-b") 
    11.             const _component_component_c = _resolveComponent("component-c") 
    12.  
    13.             return (_openBlock(), 
    14.             _createBlock(_Fragment, null, [ 
    15.               _createVNode(_component_component_a),  
    16.               _createVNode(_component_component_b),  
    17.               _createVNode(_component_component_c)], 64)) 
    18.         } 
    19.     } 
    20. }) 

    在獲取到組件之后,會通過 _createVNode 函數(shù)創(chuàng)建 VNode 節(jié)點。然而,關(guān)于 VNode 是如何被渲染成真實的 DOM 元素這個過程,阿寶哥就不繼續(xù)往下介紹了,后續(xù)會寫專門的文章來單獨介紹這塊的內(nèi)容,接下來我們將介紹動態(tài)組件的相關(guān)內(nèi)容。

    二、動態(tài)組件

    在 Vue 3 中為我們提供了一個 component 內(nèi)置組件,該組件可以渲染一個 “元組件” 為動態(tài)組件。根據(jù) is 的值,來決定哪個組件被渲染。如果 is 的值是一個字符串,它既可以是 HTML 標簽名稱也可以是組件名稱。對應(yīng)的使用示例如下:

     
     
     
     
    1.  
    2.  
    3.  
    4.  
    5.  
    6.  
    7.  
    8.  
    9.  
    10.  
    11.  

    2.1 綁定字符串類型

    介紹完 component 內(nèi)置組件,我們來舉個簡單的示例:

     
     
     
     
    1.  
    2.    
    3.       v-for="tab in tabs" 
    4.       :key="tab" 
    5.       @click="currentTab = 'tab-' + tab.toLowerCase()"> 
    6.       {{ tab }} 
    7.     
    8.     
     
  •  
  • 在以上代碼中,我們通過 app.component 方法全局注冊了 tab-home 和 tab-my 2 個組件。此外,在模板中,我們使用了 component 內(nèi)置組件,該組件的 is 屬性綁定了 data 對象的 currentTab 屬性,該屬性的類型是字符串。當用戶點擊 Tab 按鈕時,會動態(tài)更新 currentTab 的值,從而實現(xiàn)動態(tài)切換組件的功能。以上示例成功運行后的結(jié)果如下圖所示:

    看到這里你會不會覺得 component 內(nèi)置組件挺神奇的,感興趣的小伙伴繼續(xù)跟阿寶哥一起,來揭開它背后的秘密。下面我們利用 Vue 3 Template Explorer 在線工具,看一下 模板編譯的結(jié)果:

     
     
     
     
    1. const _Vue = Vue 
    2.  
    3. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
    4.   with (_ctx) { 
    5.     const { resolveDynamicComponent: _resolveDynamicComponent, openBlock: _openBlock,  
    6.       createBlock: _createBlock } = _Vue 
    7.     return (_openBlock(), _createBlock(_resolveDynamicComponent(currentTab))) 
    8.   } 

    通過觀察生成的渲染函數(shù),我們發(fā)現(xiàn)了一個 resolveDynamicComponent 的函數(shù),根據(jù)該函數(shù)的名稱,我們可以知道它用于解析動態(tài)組件,它被定義在 runtime-core/src/helpers/resolveAssets.ts 文件中,具體實現(xiàn)如下所示:

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. export function resolveDynamicComponent(component: unknown): VNodeTypes { 
    3.   if (isString(component)) { 
    4.     return resolveAsset(COMPONENTS, component, false) || component 
    5.   } else { 
    6.     // invalid types will fallthrough to createVNode and raise warning 
    7.     return (component || NULL_DYNAMIC_COMPONENT) as any 
    8.   } 

    在 resolveDynamicComponent 函數(shù)內(nèi)部,若 component 參數(shù)是字符串類型,則會調(diào)用前面介紹的 resolveAsset 方法來解析組件:

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. function resolveAsset( 
    3.   type: typeof COMPONENTS | typeof DIRECTIVES, 
    4.   name: string, 
    5.   warnMissing = true 
    6. ) { 
    7.   const instance = currentRenderingInstance || currentInstance 
    8.   if (instance) { 
    9.     const Component = instance.type 
    10.     // 省略大部分處理邏輯 
    11.     const res = 
    12.       // 局部注冊 
    13.       // check instance[type] first for components with mixin or extends. 
    14.       resolve(instance[type] || (Component as ComponentOptions)[type], name) || 
    15.       // 全局注冊 
    16.       resolve(instance.appContext[type], name) 
    17.     return res 
    18.   } 

    對于前面的示例來說,組件是全局注冊的,所以解析過程中會從 app.context 上下文對象的 components 屬性中獲取對應(yīng)的組件。當 currentTab 發(fā)生變化時,resolveAsset 函數(shù)就會返回不同的組件,從而實現(xiàn)動態(tài)組件的功能。

    此外,如果 resolveAsset 函數(shù)獲取不到對應(yīng)的組件,則會返回當前 component 參數(shù)的值。比如 resolveDynamicComponent('div') 將返回 'div' 字符串。

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. export const NULL_DYNAMIC_COMPONENT = Symbol() 
    3.  
    4. export function resolveDynamicComponent(component: unknown): VNodeTypes { 
    5.   if (isString(component)) { 
    6.     return resolveAsset(COMPONENTS, component, false) || component 
    7.   } else { 
    8.     return (component || NULL_DYNAMIC_COMPONENT) as any 
    9.   } 

    細心的小伙伴可能也注意到了,在 resolveDynamicComponent 函數(shù)內(nèi)部,如果 component 參數(shù)非字符串類型,則會返回 component || NULL_DYNAMIC_COMPONENT 這行語句的執(zhí)行結(jié)果,其中 NULL_DYNAMIC_COMPONENT 的值是一個 Symbol 對象。

    2.2 綁定對象類型

    了解完上述的內(nèi)容之后,我們來重新實現(xiàn)一下前面動態(tài) Tab 的功能:

     
     
     
     
    1.  
    2.    
    3.       v-for="tab in tabs" 
    4.       :key="tab" 
    5.       @click="currentTab = tab"> 
    6.      {{ tab.name }} 
    7.     
    8.     
     
  •  
  • 在以上示例中,component 內(nèi)置組件的 is 屬性綁定了 currentTab 對象的 component 屬性,該屬性的值是一個對象。當用戶點擊 Tab 按鈕時,會動態(tài)更新 currentTab 的值,導(dǎo)致 currentTab.component 的值也發(fā)生變化,從而實現(xiàn)動態(tài)切換組件的功能。需要注意的是,每次切換的時候,都會重新創(chuàng)建動態(tài)組件。但在某些場景下,你會希望保持這些組件的狀態(tài),以避免反復(fù)重渲染導(dǎo)致的性能問題。

    對于這個問題,我們可以使用 Vue 3 的另一個內(nèi)置組件 —— keep-alive,將動態(tài)組件包裹起來。比如:

     
     
     
     
    1.  
    2.     
    3.    

    keep-alive 內(nèi)置組件的主要作用是用于保留組件狀態(tài)或避免重新渲染,使用它包裹動態(tài)組件時,會緩存不活動的組件實例,而不是銷毀它們。關(guān)于 keep-alive 組件的內(nèi)部工作原理,阿寶哥后面會寫專門的文章來分析它,對它感興趣的小伙伴記得關(guān)注 Vue 3.0 進階 系列喲。

    三、阿寶哥有話說

    3.1 除了 component 內(nèi)置組件外,還有哪些內(nèi)置組件?

    在 Vue 3 中除了本文介紹的 component 和 keep-alive 內(nèi)置組件之外,還提供了 transition、transition-group 、slot 和 teleport 內(nèi)置組件。

    3.2 注冊全局組件與局部組件有什么區(qū)別?

    注冊全局組件

     
     
     
     
    1. const { createApp, h } = Vue 
    2. const app = createApp({}); 
    3. app.component('component-a', { 
    4.   template: "

      我是組件A

    5. }); 

    使用 app.component 方法注冊的全局的組件,被保存到 app 應(yīng)用對象的上下文對象中。而通過組件對象 components 屬性注冊的局部組件是保存在組件實例中。

    注冊局部組件

     
     
     
     
    1. const { createApp, h } = Vue 
    2. const app = createApp({}); 
    3. const componentA = () => h('div', '我是組件A'); 
    4. app.component('component-b', { 
    5.   components: { 
    6.     'component-a': componentA 
    7.   }, 
    8.   template: `
       
    9.     我是組件B,內(nèi)部使用了組件A 
    10.          
    11.   
  • }) 
  • 解析全局注冊和局部注冊的組件

     
     
     
     
    1. // packages/runtime-core/src/helpers/resolveAssets.ts 
    2. function resolveAsset( 
    3.   type: typeof COMPONENTS | typeof DIRECTIVES, 
    4.   name: string, 
    5.   warnMissing = true 
    6. ) { 
    7.   const instance = currentRenderingInstance || currentInstance 
    8.   if (instance) { 
    9.     const Component = instance.type 
    10.     // 省略大部分處理邏輯 
    11.     const res = 
    12.       // 局部注冊 
    13.       // check instance[type] first for components with mixin or extends. 
    14.       resolve(instance[type] || (Component as ComponentOptions)[type], name) || 
    15.       // 全局注冊 
    16.       resolve(instance.appContext[type], name) 
    17.     return res 
    18.   } 

    3.3 動態(tài)組件能否綁定其他屬性?

    component 內(nèi)置組件除了支持 is 綁定之外,也支持其他屬性綁定和事件綁定:

     
     
     
     
    1.  

    這里阿寶哥使用 Vue 3 Template Explorer 這個在線工具,來編譯上述的模板:

     
     
     
     
    1. const _Vue = Vue 
    2. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
    3.   with (_ctx) { 
    4.     const { resolveDynamicComponent: _resolveDynamicComponent,  
    5.       openBlock: _openBlock, createBlock: _createBlock } = _Vue 
    6.  
    7.     return (_openBlock(), _createBlock(_resolveDynamicComponent(currentTab.component), { 
    8.       name: name, 
    9.       onClick: sayHi 
    10.     }, null, 8 /* PROPS */, ["name", "onClick"])) 
    11.   } 

    觀察以上的渲染函數(shù)可知,除了 is 綁定會被轉(zhuǎn)換為 _resolveDynamicComponent 函數(shù)調(diào)用之外,其他的屬性綁定都會被正常解析為 props 對象。

    四、參考資源

    Vue 3 官網(wǎng) - 應(yīng)用 API

    Vue 3 官網(wǎng) - 內(nèi)置組件


    當前標題:Vue3.0進階之動態(tài)組件探秘
    分享地址:http://m.5511xx.com/article/cdidgse.html