自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue 3.0 進(jìn)階之依賴(lài)注入探秘

開(kāi)發(fā) 前端
為了讓大家能夠更深入地理解 provide/inject 特性,阿寶哥從源碼角度分析了 provide 和 inject 函數(shù)的具體實(shí)現(xiàn)。

 [[384053]]

本文轉(zhuǎn)載自微信公眾號(hào)「全棧修仙之路」,作者阿寶哥。轉(zhuǎn)載本文請(qǐng)聯(lián)系全棧修仙之路公眾號(hào)。  

本文是 Vue 3.0 進(jìn)階系列 的第六篇文章,在這篇文章中,阿寶哥將帶大家一起探索 Vue 3 中的依賴(lài)注入功能。

使用過(guò) Angular 的小伙伴對(duì) 依賴(lài)注入 應(yīng)該不會(huì)陌生,依賴(lài)注入簡(jiǎn)稱(chēng)為 DI(Dependency Injection)。組件之間的依賴(lài)關(guān)系由容器在運(yùn)行期決定,形象的說(shuō),即由容器動(dòng)態(tài)的將某個(gè)依賴(lài)關(guān)系注入到組件之中。依賴(lài)注入的目的并非為軟件系統(tǒng)帶來(lái)更多功能,而是為了提升組件重用的頻率,并為系統(tǒng)搭建一個(gè)靈活、可擴(kuò)展的平臺(tái)。

在 Vue 3.0 中,為我們提供了簡(jiǎn)單的依賴(lài)注入功能 —— provide/inject。它們解決了以下問(wèn)題:有一些深度嵌套的組件,而深層的子組件只需要父組件的部分內(nèi)容。在這種情況下,如果仍然將 prop 沿著組件鏈逐級(jí)傳遞下去,這樣使用起來(lái)會(huì)很麻煩。

為了解決上述問(wèn)題,Vue 提供了 provide 和 inject。使用 provide/inject 之后,無(wú)論組件層次結(jié)構(gòu)有多深,父組件都可以作為其所有子組件的依賴(lài)提供者。

(圖片來(lái)源 —— https://v3.cn.vuejs.org/guide/component-provide-inject.html)

由上圖可知,在父組件上通過(guò) provide 提供數(shù)據(jù),子組件通過(guò) inject 注入數(shù)據(jù)。介紹完 provide/inject 的作用之后,我們來(lái)看一下具體的示例。

一、Provide/Inject 使用示例

在使用 provide/inject 特性時(shí),你可以通過(guò) provide 和 inject 選項(xiàng)的方式來(lái)使用它。這種方式官方文檔已經(jīng)有介紹了,這里阿寶哥將介紹另一種使用方式,即在組合式 API 的 setup 組件選項(xiàng)中,通過(guò) provide 和 inject 函數(shù)的方式來(lái)實(shí)現(xiàn)依賴(lài)注入。

  1. <div id="app"></div> 
  2. <script> 
  3.    const { createApp, h, provide, inject } = Vue 
  4.    const app = createApp({ 
  5.      render: () => h(Provider) 
  6.    }) 
  7.     
  8.    const Provider = { 
  9.      setup() { 
  10.        provide('name''阿寶哥'
  11.        return () => h(Middle) 
  12.      } 
  13.    } 
  14.  
  15.    const Middle = { 
  16.      render: () => h(Consumer) 
  17.    } 
  18.  
  19.    const Consumer = { 
  20.      setup() { 
  21.        const name = inject('name'
  22.        return () => `大家好,我是${name}!` 
  23.      } 
  24.    } 
  25.    app.mount('#app')  
  26. </script> 

在以上示例中,在 Provider 組件內(nèi)通過(guò) provide 函數(shù)配置了數(shù)據(jù),而在 Consumer 組件中通過(guò) inject 函數(shù)獲取 Provider 組件中已配置的數(shù)據(jù)。需要注意的是,示例中的 Consumer 組件是作為 Provider 組件的孫組件。因此,通過(guò)使用 provide/inject 提供的依賴(lài)注入功能,我們實(shí)現(xiàn)了數(shù)據(jù)的跨層級(jí)傳遞。

介紹完 provide 和 inject 函數(shù)的基本使用之后,接下來(lái)阿寶哥將帶大家一起來(lái)揭開(kāi)它們背后的秘密。

二、Provide 函數(shù)

在分析 provide 函數(shù)之前,我們先來(lái)回顧一下它的用法:

  1. const Provider = { 
  2.   setup() { 
  3.     provide('name''阿寶哥'
  4.     return () => h(Middle) 
  5.   } 

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

  1. // packages/runtime-core/src/apiInject.ts 
  2. export interface InjectionKey<T> extends Symbol {} 
  3.  
  4. export function provide<T>(key: InjectionKey<T> | string | number, value: T) { 
  5.   if (!currentInstance) { 
  6.     if (__DEV__) { 
  7.       warn(`provide() can only be used inside setup().`) 
  8.     } 
  9.   } else { 
  10.     let provides = currentInstance.provides 
  11.     // 默認(rèn)情況下,組件實(shí)例會(huì)繼承于它父組件實(shí)例的 provides 對(duì)象,當(dāng)它需要為本身提供 
  12.     // 值是,它將使用父組件的 provides 對(duì)象作為原型對(duì)象來(lái)創(chuàng)建屬于它自己的 provides 
  13.     // 對(duì)象。這樣的話(huà),`inject` 函數(shù)就可以簡(jiǎn)單地從直接父對(duì)象中查找需注入的值,并讓原型鏈 
  14.     // 來(lái)完成這個(gè)工作。 
  15.     const parentProvides = 
  16.       currentInstance.parent && currentInstance.parent.provides 
  17.     if (parentProvides === provides) { 
  18.       provides = currentInstance.provides = Object.create(parentProvides) 
  19.     } 
  20.     // TS 不允許使用 symbol 作為索引類(lèi)型 
  21.     provides[key as string] = value 
  22.   } 

通過(guò)觀(guān)察以上的代碼,我們可以得出以下結(jié)論:

  • provide 函數(shù)只能使用在組合式 API 的 setup 函數(shù)中。
  • 組件實(shí)例上會(huì)有一個(gè) provides 屬性,通過(guò) provide 配置的數(shù)據(jù),最終會(huì)被保存到組件的 provides 屬性中。
  • provide 函數(shù)支持 3 種類(lèi)型作為 key,即 InjectionKey | string | number,其中 InjectionKey 類(lèi)型是 Symbol 類(lèi)型的子類(lèi)型。

在以上代碼中,我們見(jiàn)到了 currentInstance 對(duì)象,那么這個(gè)對(duì)象內(nèi)部的結(jié)構(gòu)是怎樣的呢?為了一探究竟,我們?cè)?provide 函數(shù)內(nèi)部加個(gè)斷點(diǎn):

由上圖可知, currentInstance 是一個(gè)含有多種屬性的普通對(duì)象。其中 bc(BEFORE_CREATE)、bm(BEFORE_MOUNT) 和 bu(BEFORE_UPDATE) 是與生命周期相關(guān)的鉤子。那么問(wèn)題又來(lái)了,currentInstance 對(duì)象是怎么創(chuàng)建的?看過(guò)之前 Vue 3.0 進(jìn)階系列文章的小伙伴,可能對(duì) createComponentInstance 函數(shù)有點(diǎn)印象,該函數(shù)的作用就是創(chuàng)建組件實(shí)例,具體代碼如下所示:

  1. // packages/runtime-core/src/component.ts 
  2. export function createComponentInstance( 
  3.   vnode: VNode, 
  4.   parent: ComponentInternalInstance | null
  5.   suspense: SuspenseBoundary | null 
  6. ) { 
  7.   const type = vnode.type as ConcreteComponent 
  8.   // inherit parent app context - or - if root, adopt from root vnode 
  9.   const appContext = 
  10.     (parent ? parent.appContext : vnode.appContext) || emptyAppContext 
  11.  
  12.   const instance: ComponentInternalInstance = { 
  13.     uid: uid++, vnode, type, 
  14.     parent, appContext, 
  15.     root: null!, // to be immediately set 
  16.     subTree: null!, // will be set synchronously right after creation 
  17.     updatenull!, // will be set synchronously right after creation 
  18.     render: null, effects: null
  19.     provides: parent ? parent.provides : Object.create(appContext.provides), 
  20.     bc, bm, bu 
  21.     // 省略大部分屬性 
  22.   } 
  23.   // 省略部分代碼 
  24.   instance.root = parent ? parent.root : instance 
  25.   instance.emit = emit.bind(null, instance) 
  26.   return instance 

需要注意的是,如果當(dāng)前組件 parent 屬性的值不為 null 時(shí),則將使用 parent.provides 的值作為組件實(shí)例 provides 屬性的屬性值。介紹完 provide 和 createComponentInstance 函數(shù),為了讓大家能夠更好地理解前面的示例,阿寶哥用一張圖來(lái)總結(jié)一下示例中組件之間的關(guān)系:

對(duì)于根組件來(lái)說(shuō),它的 parent 屬性值為 null。好的,provide 函數(shù)就先介紹到這里,下面我們來(lái)開(kāi)始介紹 inject 函數(shù)。

三、Inject 函數(shù)

同樣,在分析 inject 函數(shù)之前,我們也先來(lái)回顧一下它的用法:

  1. const Consumer = { 
  2.   setup() { 
  3.     const name = inject('name'
  4.     return () => `大家好,我是${name}!` 
  5.   } 

inject 函數(shù)與 provide 函數(shù)是互相配合的,它們都被定義在 runtime-core/src/apiInject.ts 文件中:

  1. // packages/runtime-core/src/apiInject.ts 
  2. export function inject( 
  3.   key: InjectionKey<any> | string, 
  4.   defaultValue?: unknown, 
  5.   treatDefaultAsFactory = false 
  6. ) { 
  7.   const instance = currentInstance || currentRenderingInstance // 獲取當(dāng)前實(shí)例 
  8.   if (instance) { 
  9.     // to support `app.use` plugins, 
  10.     // fallback to appContext's `provides` if the intance is at root 
  11.     const provides = 
  12.       instance.parent == null 
  13.         ? instance.vnode.appContext && instance.vnode.appContext.provides 
  14.         : instance.parent.provides 
  15.  
  16.     if (provides && (key as string | symbol) in provides) { 
  17.       // TS doesn't allow symbol as index type 
  18.       return provides[key as string] 
  19.     } else if (arguments.length > 1) { 
  20.       return treatDefaultAsFactory && isFunction(defaultValue) 
  21.         ? defaultValue() 
  22.         : defaultValue 
  23.     } else if (__DEV__) { 
  24.       warn(`injection "${String(key)}" not found.`) 
  25.     } 
  26.   } else if (__DEV__) { 
  27.     warn(`inject() can only be used inside setup() or functional components.`) 
  28.   } 

在 inject 函數(shù)中,我們可以清楚地看到如果當(dāng)前實(shí)例的 parent 屬性為 null 時(shí),則會(huì)從 appContext 上下文中獲取 provides 對(duì)象,否則將從當(dāng)前實(shí)例的父組件實(shí)例中獲取 provides 對(duì)象。

對(duì)于我們的示例來(lái)說(shuō),在獲取到 provides 對(duì)象后,就會(huì)判斷 name 屬性是否存在于當(dāng)前的 provides 對(duì)象中,此時(shí)該對(duì)象是 { name: "阿寶哥"},所以會(huì)直接返回 "阿寶哥"。

此外,通過(guò)觀(guān)察 inject 函數(shù),我們還可以得出以下結(jié)論:

  • inject 函數(shù)的第二個(gè)參數(shù)是一個(gè)可選參數(shù) —— defaultValue?: unknown,用于設(shè)置默認(rèn)值或默認(rèn)值工廠(chǎng)。即在 provides 對(duì)象上找不到 key 對(duì)應(yīng)值的時(shí)候,可以使用默認(rèn)值或默認(rèn)值工廠(chǎng)的返回值來(lái)代替。
  • inject 函數(shù)只能在 setup() 或函數(shù)組件中使用。

四、App 對(duì)象中的 provide API

在創(chuàng)建完 Vue 3 應(yīng)用對(duì)象之后,我們可以使用該對(duì)象提供的 provide 方法。該方法設(shè)置一個(gè)可以被注入到應(yīng)用范圍內(nèi)所有組件中的值。之后,組件就可以使用 inject 來(lái)接收 provide 的值。

  1. import { createApp } from 'vue' 
  2.  
  3. const app = createApp({ 
  4.   inject: ['name'], 
  5.   template: ` 
  6.     <div> 
  7.       {{ name }} 
  8.     </div> 
  9.   ` 
  10. }) 
  11.  
  12. app.provide('name''阿寶哥'

需要注意的是,app.provide 方法不應(yīng)該與 provide 組件選項(xiàng)或組合式 API 中的 provide 方法混淆。雖然它們也是相同的 provide/inject 機(jī)制的一部分,但是是用來(lái)配置組件 provide 的值而不是應(yīng)用 provide 的值。

介紹完 app.provide 方法之后,我們來(lái)了解一下它的實(shí)現(xiàn)。看過(guò) ”Vue 3.0 進(jìn)階“ 系列教程的小伙伴,對(duì) app 對(duì)象應(yīng)該不會(huì)陌生。因?yàn)樵谇懊娴奈恼轮?,阿寶哥已?jīng)介紹過(guò) component、directive 和 mount 等方法。接下來(lái),我們來(lái)看一下 provide 方法的具體實(shí)現(xiàn):

  1. // packages/runtime-core/src/apiCreateApp.ts 
  2. export function createAppAPI<HostElement>( 
  3.   render: RootRenderFunction, 
  4.   hydrate?: RootHydrateFunction 
  5. ): CreateAppFunction<HostElement> { 
  6.   return function createApp(rootComponent, rootProps = null) { 
  7.     const context = createAppContext() 
  8.  
  9.     // 省略大部分內(nèi)容 
  10.     const app: App = (context.app = { 
  11.       _uid: uid++, 
  12.       _context: context, 
  13.  
  14.       provide(key, value) { 
  15.         // TypeScript doesn't allow symbols as index type 
  16.         // https://github.com/Microsoft/TypeScript/issues/24587 
  17.         context.provides[key as string] = value 
  18.         return app 
  19.       } 
  20.     }) 
  21.  
  22.     return app 
  23.   } 

由以上代碼可知,在 provide 方法內(nèi)部會(huì)把 key 和 value 以鍵值對(duì)的形式保存在應(yīng)用上下文 context 對(duì)象的 provides 屬性中。

  1. // packages/runtime-core/src/apiCreateApp.ts 
  2. export function createAppContext(): AppContext { 
  3.   return { 
  4.     app: null as any
  5.     config: { ... }, 
  6.     mixins: [], 
  7.     components: {}, 
  8.     directives: {}, 
  9.     provides: Object.create(null
  10.   } 

五、阿寶哥有話(huà)說(shuō)

5.1 在嵌套的 providers 場(chǎng)景下,存在同名的 key 會(huì)怎么樣?

  1. <div id="app"></div> 
  2. <script> 
  3.    const { createApp, h, provide, inject } = Vue 
  4.    const app = createApp({ 
  5.      render: () => h(ProviderOne) 
  6.    }) 
  7.     
  8.    const ProviderOne = { 
  9.      setup() { 
  10.        provide('foo''foo'
  11.        provide('bar''bar'
  12.        return () => h(ProviderTwo) 
  13.      } 
  14.    } 
  15.  
  16.    const ProviderTwo = { 
  17.      setup() { 
  18.        provide('foo''fooOverride'
  19.        provide('baz''baz'
  20.        return () => h(Consumer) 
  21.       } 
  22.    } 
  23.  
  24.    const Consumer = { 
  25.      setup() { 
  26.        const foo = inject('foo'
  27.        const bar = inject('bar'
  28.        const baz = inject('baz'
  29.        return () => [foo, bar, baz].join(','
  30.      } 
  31.    } 
  32.    app.mount('#app')  
  33. </script> 

在以上代碼中,ProviderOne 和 ProviderTwo 組件中使用同樣的 foo 屬性名配置了 Provider。然后,我們?cè)诘讓拥? Consumer 組件中使用 inject API 分別注入了 ProviderOne 和 ProviderTwo 中配置的值。接下來(lái),我們先來(lái)看一下結(jié)果:

  1. fooOverride,bar,baz 

由以上結(jié)果可知,在嵌套 providers 的場(chǎng)景中,會(huì)就近從父組件實(shí)例中獲取對(duì)應(yīng)的值,找不到的話(huà),會(huì)往上一層層進(jìn)行查找。

5.2 通過(guò) inject 獲取響應(yīng)式的值,能否正常工作?

在某些場(chǎng)景下,我們希望往深層的子組件傳遞通過(guò)響應(yīng)式 API 創(chuàng)建的響應(yīng)式的值,那么通過(guò) inject 函數(shù)獲取的響應(yīng)式的值可以正常工作么?要弄清楚這個(gè)問(wèn)題,我們來(lái)看一個(gè)具體的示例:

  1. <div id="app"></div> 
  2. <script> 
  3.    const { createApp, h, provide, inject, ref, onMounted } = Vue 
  4.    const nameRef = ref("阿寶哥"); 
  5.    const app = createApp({ 
  6.      setup() { 
  7.        provide("name", nameRef); 
  8.        return () => h(Middle) 
  9.      } 
  10.     }) 
  11.  
  12.    const Middle = { 
  13.      render: () => h(Consumer) 
  14.    } 
  15.  
  16.    const Consumer = { 
  17.      setup() { 
  18.        const name = inject('name'
  19.        onMounted(() => { 
  20.          setTimeout(() => nameRef.value = "kakuqo", 2000); 
  21.        }) 
  22.        return () => `大家好,我是${name.value}!` 
  23.      } 
  24.   } 
  25.   app.mount('#app')  
  26. </script> 

在以上代碼中,我們通過(guò) ref API 創(chuàng)建了一個(gè) nameRef 對(duì)象,然后在根組件中通過(guò) provide 函數(shù)配置相應(yīng)的 Provider。而在 Consumer 組件的 setup 方法內(nèi),我們通過(guò) inject 函數(shù)注入了 nameRef 對(duì)象,并通過(guò) name.value 訪(fǎng)問(wèn)了該對(duì)象內(nèi)保存的值。

此外,在 setup 方法內(nèi)部,我們還使用了 onMounted 生命周期鉤子,在鉤子對(duì)應(yīng)的回調(diào)函數(shù)中,我們延遲 2S 修改 nameRef 對(duì)象的值。

以上示例成功運(yùn)行后,首先會(huì)先顯示 大家好,我是阿寶哥!,差不多 2S 后頁(yè)面會(huì)刷新為 大家好,我是kakuqo!。

5.3 是否支持 self-inject?

什么是 self-inject 呢?這里阿寶哥不做過(guò)多解釋?zhuān)覀冎苯觼?lái)看個(gè)具體的例子:

  1. <div id="app"></div> 
  2. <script> 
  3.    const { createApp, h, provide, inject } = Vue 
  4.    const app = createApp({ 
  5.      render: () => h(Provider) 
  6.    }) 
  7.     
  8.    const Provider = { 
  9.      setup() { 
  10.        provide('name''阿寶哥'
  11.        const injectedName = inject('name'
  12.        return () => h(injectedName) 
  13.      } 
  14.    } 
  15.    app.mount('#app')  
  16. </script> 

在以上代碼中,我們?cè)?Provider 組件的 setup 方法內(nèi)部先使用 provide 函數(shù)配置了相應(yīng)的 Provider,然后使用 inject 函數(shù)來(lái)獲取對(duì)應(yīng)的值。很明顯這個(gè)操作并沒(méi)有實(shí)際的意義,那么可以這樣使用么?答案是可以的,以上示例成功運(yùn)行之后,Provider 組件會(huì)被轉(zhuǎn)換為 注釋節(jié)點(diǎn)。

  1. <div id="app" data-v-app=""><!----></div> 

那么為什么會(huì)轉(zhuǎn)為注釋節(jié)點(diǎn)呢?因?yàn)?injectedName 的值為 undefined,在通過(guò) h 函數(shù)創(chuàng)建 VNode 對(duì)象的時(shí)候,會(huì)繼續(xù)調(diào)用 createVNode 函數(shù),在該函數(shù)內(nèi)部如果發(fā)現(xiàn)是 type 類(lèi)型為 falsy 值時(shí),會(huì)把 VNode 對(duì)象的類(lèi)型統(tǒng)一轉(zhuǎn)換為 Comment 類(lèi)型。

  1. // packages/runtime-core/src/vnode.ts 
  2. function _createVNode( 
  3.   type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, 
  4.   props: (Data & VNodeProps) | null = null
  5.   children: unknown = null
  6.   patchFlag: number = 0, 
  7.   dynamicProps: string[] | null = null
  8.   isBlockNode = false 
  9. ): VNode { 
  10.   if (!type || type === NULL_DYNAMIC_COMPONENT) { 
  11.     if (__DEV__ && !type) { 
  12.       warn(`Invalid vnode type when creating vnode: ${type}.`) 
  13.     } 
  14.     type = Comment 
  15.   } 
  16.   // 省略大部分代碼 

本文阿寶哥主要介紹了依賴(lài)注入的概念及作用、如何使用 Vue 3 提供的 provide/inject 特性。為了讓大家能夠更深入地理解 provide/inject 特性,阿寶哥從源碼角度分析了 provide 和 inject 函數(shù)的具體實(shí)現(xiàn)。在后續(xù)的文章中,阿寶哥將會(huì)介紹在插件中如何應(yīng)用 provide/inject 特性,感興趣的小伙伴不要錯(cuò)過(guò)喲。

六、參考資源

Vue 3 官網(wǎng) - Provide/Inject

Vue 3 官網(wǎng) - 組合式 API

 

責(zé)任編輯:武曉燕 來(lái)源: 全棧修仙之路
相關(guān)推薦

2021-02-16 16:41:45

Vue項(xiàng)目指令

2021-02-26 05:19:20

Vue 3.0 VNode虛擬

2021-02-19 23:07:02

Vue綁定組件

2021-02-22 21:49:33

Vue動(dòng)態(tài)組件

2021-02-18 08:19:21

Vue自定義Vue 3.0

2021-03-04 22:31:02

Vue進(jìn)階函數(shù)

2021-03-08 00:08:29

Vue應(yīng)用掛載

2021-03-09 22:29:46

Vue 響應(yīng)式API

2011-03-29 09:51:58

GuiceIOC

2014-01-07 14:53:37

Android開(kāi)發(fā)依賴(lài)注入Roboguice

2011-05-31 10:00:21

Android Spring 依賴(lài)注入

2011-04-15 09:44:45

Spring

2010-05-11 16:22:40

2023-07-11 09:14:12

Beanquarkus

2022-12-29 08:54:53

依賴(lài)注入JavaScript

2017-08-16 16:00:05

PHPcontainer依賴(lài)注入

2021-12-15 09:17:12

Spring依賴(lài)注入面試題

2020-10-13 08:24:31

Vue3.0系列

2019-09-18 18:12:57

前端javascriptvue.js

2022-04-30 08:50:11

控制反轉(zhuǎn)Spring依賴(lài)注入
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)