Vue3的provide和inject實現(xiàn)多級傳遞的原理解析
前言
沒有看過provide
和inject
函數(shù)源碼的小伙伴可能覺得他們實現(xiàn)數(shù)據(jù)多級傳遞非常神秘,其實他的源碼非常簡單,這篇文章歐陽來講講provide
和inject
函數(shù)是如何實現(xiàn)數(shù)據(jù)多級傳遞的。ps:本文中使用的Vue版本為3.5.13
。
看個demo
先來看個demo,這個是父組件,代碼如下:
<template> <ChildDemo /> </template> <script setup> import ChildDemo from "./child.vue"; import { ref, provide } from "vue"; // 提供響應(yīng)式的值 const count = ref(0); provide("count", count); </script>
在父組件中使用provide
為后代組件注入一個count
響應(yīng)式變量。
再來看看子組件child.vue
代碼如下:
<template> <GrandChild /> </template> <script setup> import GrandChild from "./grand-child.vue"; </script>
從上面的代碼可以看到在子組件中什么事情都沒做,只渲染了孫子組件。
我們再來看看孫子組件grand-child.vue
,代碼如下:
<script setup> import { inject } from "vue"; // 注入響應(yīng)式的值 const count = inject("count"); console.log("inject count is:", count); </script>
從上面的代碼可以看到在孫子組件中使用inject
函數(shù)拿到了父組件中注入的count
響應(yīng)式變量。
provide函數(shù)
我們先來debug看看provide函數(shù)的代碼,給父組件中的provide函數(shù)打個斷點,如下圖:
刷新頁面,此時代碼將會停留在斷點處。讓斷點走進(jìn)provide函數(shù),代碼如下:
function provide(key, value) { if (!currentInstance) { if (!!(process.env.NODE_ENV !== "production")) { warn$1(`provide() can only be used inside setup().`); } } else { let provides = currentInstance.provides; const parentProvides = currentInstance.parent && currentInstance.parent.provides; if (parentProvides === provides) { provides = currentInstance.provides = Object.create(parentProvides); } provides[key] = value; } }
首先判斷currentInstance
是否有值,如果沒有就說明當(dāng)前沒有vue實例,也就是說當(dāng)前調(diào)用provide函數(shù)的地方是不在setup函數(shù)中執(zhí)行的,然后給出警告provide只能在setup中使用。
然后走進(jìn)else邏輯中,首先從當(dāng)前vue實例中取出存的provides
屬性對象。并且通過currentInstance.parent.provides
拿到父組件vue實例中的provides
屬性對象。
這里為什么需要判斷if (parentProvides === provides)
呢?
因為在創(chuàng)建子組件時會默認(rèn)使用父組件的provides
屬性對象作為父組件的provides
屬性對象。代碼如下:
const instance: ComponentInternalInstance = { uid: uid++, vnode, type, parent, provides: parent ? parent.provides : Object.create(appContext.provides), // ...省略 }
從上面的代碼可以看到如果有父組件,那么創(chuàng)建子組件實例的時候就直接使用父組件的provides
屬性對象。
所以這里在provide函數(shù)中需要判斷if (parentProvides === provides)
,如果相等說明當(dāng)前父組件和子組件是共用的同一個provides
屬性對象。此時如果子組件調(diào)用了provide函數(shù),說明子組件需要創(chuàng)建自己的provides
屬性對象。
并且新的屬性對象還需要能夠訪問到父組件中注入的內(nèi)容,所以這里以父組件的provides
屬性對象為原型去創(chuàng)建一個新的子組件的,這樣在子組件中不僅能夠訪問到原型鏈中注入的provides
屬性對象,也能夠訪問到自己注入進(jìn)去的provides
屬性對象。
最后就是執(zhí)行provides[key] = value
將當(dāng)前注入的內(nèi)容存到provides
屬性對象中。
inject函數(shù)
我們再來看看inject函數(shù)是如何隔了一層子組件從父組件中如何取出數(shù)據(jù)的,還是一樣的套路,給孫子組件中的inject函數(shù)打個斷點。如下圖:
將斷點走進(jìn)inject函數(shù),代碼如下:
export function inject( key: InjectionKey<any> | string, defaultValue?: unknown, treatDefaultAsFactory = false, ) { // fallback to `currentRenderingInstance` so that this can be called in // a functional component const instance = currentInstance || currentRenderingInstance // also support looking up from app-level provides w/ `app.runWithContext()` if (instance || currentApp) { const provides = currentApp ? currentApp._context.provides : instance ? instance.parent == null ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides : undefined if (provides && key in provides) { return provides[key] } else if (arguments.length > 1) { return treatDefaultAsFactory && isFunction(defaultValue) ? defaultValue.call(instance && instance.proxy) : defaultValue } else if (__DEV__) { warn(`injection "${String(key)}" not found.`) } } else if (__DEV__) { warn(`inject() can only be used inside setup() or functional components.`) } }
首先拿到當(dāng)前渲染的vue實例賦值給本地變量instance
。接著使用if (instance || currentApp)
判斷當(dāng)前是否有vue實例,如果沒有看看有沒有使用app.runWithContext
手動注入了上下文,如果注入了那么currentApp
就有值。
接著就是一串三元表達(dá)式,如果使用app.runWithContext
手動注入了上下文,那么就優(yōu)先從注入的上下文中取出provides
屬性對象。
如果沒有那么就看當(dāng)前組件是否滿足instance.parent == null
,也就是說當(dāng)前組件是否是根節(jié)點。如果是根節(jié)點就取app中注入的provides
屬性對象。
如果上面的都不滿足就去取父組件中注入的provides
屬性對象,前面我們講過了在inject函數(shù)階段,如果子組件內(nèi)沒有使用inject函數(shù),那么就會直接使用父組件的provides
屬性對象。如果子組件中使用了inject函數(shù),那么就以父組件的provides
屬性對象為原型去創(chuàng)建一個新的子組件的provides
屬性對象,從而形成一條原型鏈。
所以這里的孫子節(jié)點的provides
屬性對象中當(dāng)然就能夠拿到父組件中注入的count
響應(yīng)式變量,那么if (provides && key in provides)
就滿足條件,最后會走到return provides[key]
中將父組件中注入的響應(yīng)式變量count
原封不動的返回。
還有就是如果我們inject一個沒有使用provide存入的key,并且傳入了第二個參數(shù)defaultValue
,此時else if (arguments.length > 1)
就滿足條件了。
在里面會去判斷是否傳入第三個參數(shù)treatDefaultAsFactory
,如果這個參數(shù)的值為true,說明第二個參數(shù)defaultValue
可能是一個工廠函數(shù)。那么就執(zhí)行defaultValue.call(instance && instance.proxy)
將defaultValue
的當(dāng)中工廠函數(shù)的執(zhí)行結(jié)果進(jìn)行返回。
如果第三個參數(shù)treatDefaultAsFactory
的值不為true,那么就直接將第二個參數(shù)defaultValue
當(dāng)做默認(rèn)值返回。
總結(jié)
這篇文章講了使用provide
和inject
函數(shù)是如何實現(xiàn)數(shù)據(jù)多級傳遞的。
在創(chuàng)建vue組件實例時,子組件的provides
屬性對象會直接使用父組件的provides
屬性對象。如果在子組件中使用了provide
函數(shù),那么會以父組件的provides
屬性對象為原型創(chuàng)建一個新的provides
屬性對象,并且將provide
函數(shù)中注入的內(nèi)容塞到新的provides
屬性對象中,從而形成了原型鏈。
在孫子組件中,他的parent就是子組件。前面我們講過了如果沒有在組件內(nèi)使用provide
注入東西(很明顯這里的子組件確實沒有注入任何東西),那么就會直接使用他的父組件的provides
屬性對象,所以這里的子組件是直接使用的是父組件中的provides
屬性對象。所以在孫子組件中可以直接使用inject
函數(shù)拿到父組件中注入的內(nèi)容。
到此這篇關(guān)于來談?wù)刅ue3的provide和inject實現(xiàn)多級傳遞的原理的文章就介紹到這了,更多相關(guān)Vue3的provide和inject多級傳遞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue實現(xiàn)Excel預(yù)覽功能使用場景示例詳解
這篇文章主要為大家介紹了Vue實現(xiàn)Excel預(yù)覽功能使用場景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Vue使用axios進(jìn)行g(shù)et請求拼接參數(shù)的2種方式詳解
axios中post請求都是要求攜帶參數(shù)進(jìn)行請求,這篇文章主要給大家介紹了關(guān)于Vue使用axios進(jìn)行g(shù)et請求拼接參數(shù)的2種方式,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01vue3+element?Plus實現(xiàn)表格前端分頁完整示例
這篇文章主要給大家介紹了關(guān)于vue3+element?Plus實現(xiàn)表格前端分頁的相關(guān)資料,雖然很多時候后端會把分頁,搜索,排序都做好,但是有些返回數(shù)據(jù)并不多的頁面,或者其他原因不能后端分頁的通常會前端處理,需要的朋友可以參考下2023-08-08關(guān)于vue3使用particles粒子特效的問題
這篇文章主要介紹了關(guān)于vue3使用particles粒子特效的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06