中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue 3.0中怎么實現依賴注入

發布時間:2021-07-21 13:54:34 來源:億速云 閱讀:537 作者:Leah 欄目:web開發

這篇文章將為大家詳細講解有關Vue 3.0中怎么實現依賴注入,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

使用過 Angular 的小伙伴對 依賴注入 應該不會陌生,依賴注入簡稱為 DI(Dependency  Injection)。組件之間的依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系注入到組件之中。依賴注入的目的并非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,并為系統搭建一個靈活、可擴展的平臺。

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

為了解決上述問題,Vue 提供了 provide 和 inject。使用 provide/inject  之后,無論組件層次結構有多深,父組件都可以作為其所有子組件的依賴提供者。

Vue 3.0中怎么實現依賴注入

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

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

一、Provide/Inject 使用示例

在使用 provide/inject 特性時,你可以通過 provide 和 inject  選項的方式來使用它。這種方式官方文檔已經有介紹了,這里阿寶哥將介紹另一種使用方式,即在組合式 API 的 setup 組件選項中,通過 provide 和  inject 函數的方式來實現依賴注入。

<div id="app"></div> <script>    const { createApp, h, provide, inject } = Vue    const app = createApp({      render: () => h(Provider)    })        const Provider = {      setup() {        provide('name', '阿寶哥')        return () => h(Middle)      }    }     const Middle = {      render: () => h(Consumer)    }     const Consumer = {      setup() {        const name = inject('name')        return () => `大家好,我是${name}!`      }    }    app.mount('#app')  </script>

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

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

二、Provide 函數

在分析 provide 函數之前,我們先來回顧一下它的用法:

const Provider = {   setup() {     provide('name', '阿寶哥')     return () => h(Middle)   } }

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

// packages/runtime-core/src/apiInject.ts export interface InjectionKey<T> extends Symbol {}  export function provide<T>(key: InjectionKey<T> | string | number, value: T) {   if (!currentInstance) {     if (__DEV__) {       warn(`provide() can only be used inside setup().`)     }   } else {     let provides = currentInstance.provides     // 默認情況下,組件實例會繼承于它父組件實例的 provides 對象,當它需要為本身提供     // 值是,它將使用父組件的 provides 對象作為原型對象來創建屬于它自己的 provides     // 對象。這樣的話,`inject` 函數就可以簡單地從直接父對象中查找需注入的值,并讓原型鏈     // 來完成這個工作。     const parentProvides =       currentInstance.parent && currentInstance.parent.provides     if (parentProvides === provides) {       provides = currentInstance.provides = Object.create(parentProvides)     }     // TS 不允許使用 symbol 作為索引類型     provides[key as string] = value   } }

通過觀察以上的代碼,我們可以得出以下結論:

  • provide 函數只能使用在組合式 API 的 setup 函數中。

  • 組件實例上會有一個 provides 屬性,通過 provide 配置的數據,最終會被保存到組件的 provides 屬性中。

  • provide 函數支持 3 種類型作為 key,即 InjectionKey

    | string | number,其中 InjectionKey  類型是 Symbol 類型的子類型。

在以上代碼中,我們見到了 currentInstance 對象,那么這個對象內部的結構是怎樣的呢?為了一探究竟,我們在 provide  函數內部加個斷點:

Vue 3.0中怎么實現依賴注入

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

// packages/runtime-core/src/component.ts export function createComponentInstance(   vnode: VNode,   parent: ComponentInternalInstance | null,   suspense: SuspenseBoundary | null ) {   const type = vnode.type as ConcreteComponent   // inherit parent app context - or - if root, adopt from root vnode   const appContext =     (parent ? parent.appContext : vnode.appContext) || emptyAppContext    const instance: ComponentInternalInstance = {     uid: uid++, vnode, type,     parent, appContext,     root: null!, // to be immediately set     subTree: null!, // will be set synchronously right after creation     update: null!, // will be set synchronously right after creation     render: null, effects: null,     provides: parent ? parent.provides : Object.create(appContext.provides),     bc, bm, bu     // 省略大部分屬性   }   // 省略部分代碼   instance.root = parent ? parent.root : instance   instance.emit = emit.bind(null, instance)   return instance }

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

Vue 3.0中怎么實現依賴注入

對于根組件來說,它的 parent 屬性值為 null。好的,provide 函數就先介紹到這里,下面我們來開始介紹 inject 函數。

三、Inject 函數

同樣,在分析 inject 函數之前,我們也先來回顧一下它的用法:

const Consumer = {   setup() {     const name = inject('name')     return () => `大家好,我是${name}!`   } }

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

// packages/runtime-core/src/apiInject.ts export function inject(   key: InjectionKey<any> | string,   defaultValue?: unknown,   treatDefaultAsFactory = false ) {   const instance = currentInstance || currentRenderingInstance // 獲取當前實例   if (instance) {     // to support `app.use` plugins,     // fallback to appContext's `provides` if the intance is at root     const provides =       instance.parent == null         ? instance.vnode.appContext && instance.vnode.appContext.provides         : instance.parent.provides      if (provides && (key as string | symbol) in provides) {       // TS doesn't allow symbol as index type       return provides[key as string]     } else if (arguments.length > 1) {       return treatDefaultAsFactory && isFunction(defaultValue)         ? defaultValue()         : defaultValue     } else if (__DEV__) {       warn(`injection "${String(key)}" not found.`)     }   } else if (__DEV__) {     warn(`inject() can only be used inside setup() or functional components.`)   } }

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

對于我們的示例來說,在獲取到 provides 對象后,就會判斷 name 屬性是否存在于當前的 provides 對象中,此時該對象是 { name:  "阿寶哥"},所以會直接返回 "阿寶哥"。

Vue 3.0中怎么實現依賴注入

此外,通過觀察 inject 函數,我們還可以得出以下結論:

  • inject 函數的第二個參數是一個可選參數 &mdash;&mdash; defaultValue?: unknown,用于設置默認值或默認值工廠。即在 provides  對象上找不到 key 對應值的時候,可以使用默認值或默認值工廠的返回值來代替。

  • inject 函數只能在 setup() 或函數組件中使用。

四、App 對象中的 provide API

在創建完 Vue 3 應用對象之后,我們可以使用該對象提供的 provide  方法。該方法設置一個可以被注入到應用范圍內所有組件中的值。之后,組件就可以使用 inject 來接收 provide 的值。

import { createApp } from 'vue'  const app = createApp({   inject: ['name'],   template: `     <div>       {{ name }}     </div>   ` })  app.provide('name', '阿寶哥')

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

介紹完 app.provide 方法之后,我們來了解一下它的實現。看過 ”Vue 3.0 進階“ 系列教程的小伙伴,對 app  對象應該不會陌生。因為在前面的文章中,阿寶哥已經介紹過 component、directive 和 mount 等方法。接下來,我們來看一下 provide  方法的具體實現:

// packages/runtime-core/src/apiCreateApp.ts export function createAppAPI<HostElement>(   render: RootRenderFunction,   hydrate?: RootHydrateFunction ): CreateAppFunction<HostElement> {   return function createApp(rootComponent, rootProps = null) {     const context = createAppContext()      // 省略大部分內容     const app: App = (context.app = {       _uid: uid++,       _context: context,        provide(key, value) {         // TypeScript doesn't allow symbols as index type         // https://github.com/Microsoft/TypeScript/issues/24587         context.provides[key as string] = value         return app       }     })      return app   } }

由以上代碼可知,在 provide 方法內部會把 key 和 value 以鍵值對的形式保存在應用上下文 context 對象的 provides  屬性中。

// packages/runtime-core/src/apiCreateApp.ts export function createAppContext(): AppContext {   return {     app: null as any,     config: { ... },     mixins: [],     components: {},     directives: {},     provides: Object.create(null)   } }

五、阿寶哥有話說

5.1 在嵌套的 providers 場景下,存在同名的 key 會怎么樣?

<div id="app"></div> <script>    const { createApp, h, provide, inject } = Vue    const app = createApp({      render: () => h(ProviderOne)    })        const ProviderOne = {      setup() {        provide('foo', 'foo')        provide('bar', 'bar')        return () => h(ProviderTwo)      }    }     const ProviderTwo = {      setup() {        provide('foo', 'fooOverride')        provide('baz', 'baz')        return () => h(Consumer)       }    }     const Consumer = {      setup() {        const foo = inject('foo')        const bar = inject('bar')        const baz = inject('baz')        return () => [foo, bar, baz].join(',')      }    }    app.mount('#app')  </script>

在以上代碼中,ProviderOne 和 ProviderTwo 組件中使用同樣的 foo 屬性名配置了 Provider。然后,我們在底層的  Consumer 組件中使用 inject API 分別注入了 ProviderOne 和 ProviderTwo  中配置的值。接下來,我們先來看一下結果:

fooOverride,bar,baz

由以上結果可知,在嵌套 providers 的場景中,會就近從父組件實例中獲取對應的值,找不到的話,會往上一層層進行查找。

5.2 通過 inject 獲取響應式的值,能否正常工作?

在某些場景下,我們希望往深層的子組件傳遞通過響應式 API 創建的響應式的值,那么通過 inject  函數獲取的響應式的值可以正常工作么?要弄清楚這個問題,我們來看一個具體的示例:

<div id="app"></div> <script>    const { createApp, h, provide, inject, ref, onMounted } = Vue    const nameRef = ref("阿寶哥");    const app = createApp({      setup() {        provide("name", nameRef);        return () => h(Middle)      }     })     const Middle = {      render: () => h(Consumer)    }     const Consumer = {      setup() {        const name = inject('name')        onMounted(() => {          setTimeout(() => nameRef.value = "kakuqo", 2000);        })        return () => `大家好,我是${name.value}!`      }   }   app.mount('#app')  </script>

在以上代碼中,我們通過 ref API 創建了一個 nameRef 對象,然后在根組件中通過 provide 函數配置相應的 Provider。而在  Consumer 組件的 setup 方法內,我們通過 inject 函數注入了 nameRef 對象,并通過 name.value  訪問了該對象內保存的值。

此外,在 setup 方法內部,我們還使用了 onMounted 生命周期鉤子,在鉤子對應的回調函數中,我們延遲 2S 修改 nameRef  對象的值。

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

5.3 是否支持 self-inject?

什么是 self-inject 呢?這里阿寶哥不做過多解釋,我們直接來看個具體的例子:

<div id="app"></div> <script>    const { createApp, h, provide, inject } = Vue    const app = createApp({      render: () => h(Provider)    })        const Provider = {      setup() {        provide('name', '阿寶哥')        const injectedName = inject('name')        return () => h(injectedName)      }    }    app.mount('#app')  </script>

在以上代碼中,我們在 Provider 組件的 setup 方法內部先使用 provide 函數配置了相應的 Provider,然后使用 inject  函數來獲取對應的值。很明顯這個操作并沒有實際的意義,那么可以這樣使用么?答案是可以的,以上示例成功運行之后,Provider 組件會被轉換為 注釋節點。

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

那么為什么會轉為注釋節點呢?因為 injectedName 的值為 undefined,在通過 h 函數創建 VNode 對象的時候,會繼續調用  createVNode 函數,在該函數內部如果發現是 type 類型為 falsy 值時,會把 VNode 對象的類型統一轉換為 Comment 類型。

// packages/runtime-core/src/vnode.ts function _createVNode(   type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,   props: (Data & VNodeProps) | null = null,   children: unknown = null,   patchFlag: number = 0,   dynamicProps: string[] | null = null,   isBlockNode = false ): VNode {   if (!type || type === NULL_DYNAMIC_COMPONENT) {     if (__DEV__ && !type) {       warn(`Invalid vnode type when creating vnode: ${type}.`)     }     type = Comment   }   // 省略大部分代碼 }

關于Vue 3.0中怎么實現依賴注入就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

vue
AI

彭水| 乳山市| 郧西县| 沧州市| 青岛市| 永川市| 灌阳县| 潢川县| 临颍县| 临海市| 南充市| 永定县| 鹤庆县| 曲阜市| 报价| 绍兴县| 潍坊市| 安宁市| 巩留县| 清苑县| 苗栗县| 荃湾区| 南郑县| 嘉义市| 水城县| 永川市| 临泉县| 鸡泽县| 彭阳县| 安仁县| 胶南市| 湘西| 怀化市| 清水河县| 元江| 舞阳县| 沁水县| 浦县| 贵南县| 偏关县| 宜宾市|