Vue3實(shí)現(xiàn)provide/inject的示例詳解
實(shí)現(xiàn)思路
場(chǎng)景分析
可以在全局父組件里通過(guò)provide
將所有需要對(duì)外提供的全局屬性方法進(jìn)行跨組件透?jìng)鳎瑹o(wú)論嵌套多深的子組件都可以進(jìn)行inject
注入使用,包括不限于計(jì)算屬性、方法等,甚至將整個(gè)app.vue實(shí)例進(jìn)行相應(yīng)的透?jìng)鳎?/p>
測(cè)試用例
// example/apiinject/App.js import { h, provide, inject } from "../../lib/guide-mini-vue.esm.js"; const Provider = { name: 'Provider', setup(){ provide("foo","fooVal"); provide("bar","barVal"); }, render() { return h("div",{},[ h("hr",{},""), h("p",{},"Provider"), h("hr",{},""), h(Provider2)] ) } } const Provider2 = { name: 'Provider2', setup(){ provide("foo","Provider2-foo"); const foo = inject("foo","default Provider2-foo") return{ foo } }, render() { return h("div",{},[ h("p",{},`Provider2 foo:${this.foo}`), h("hr",{},""), h(Consumer)] ) } } const Consumer = { name: "Consumer", setup() { const foo = inject("foo"); const bar = inject("bar"); const bars = inject("bars",'bares default'); const barfn = inject("barss",() => 'bares default fn'); return { foo, bar, bars, barfn } }, render(){ return h("div",{},`Consumer: - ${this.foo} - ${this.bar} - ${this.bars} - ${this.barfn}`) } } export default { name: "App", setup() {}, render() { return h("div",{},[ h("h1",{},"apiInject - apiProvide"), h(Provider) ]) } }
// example/apiinject/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>API-Inject+provide</title> </head> <body> <div id="app"></div> <!-- <script src="main.js" type="module"></script> --> <script type="module"> import { createApp } from "../../lib/guide-mini-vue.esm.js" import App from "./App.js"; const rootContainer = document.querySelector("#app"); createApp(App).mount(rootContainer); </script> </body> </html>
渲染結(jié)果
步驟解析
當(dāng)調(diào)用provide
的時(shí)候需要將provide
中的key/value
掛載到組件實(shí)例的provides
屬性上,當(dāng)子組件調(diào)用Inject
的時(shí)候,通過(guò)獲取到子組件的實(shí)例進(jìn)而通過(guò)parent
得到父組件實(shí)例,然后通過(guò)父組件實(shí)例上的provides
對(duì)象獲取到相應(yīng)key
對(duì)應(yīng)的value
1.實(shí)現(xiàn)getCurrentInstance
函數(shù)
用于獲取組件實(shí)例實(shí)例,父組件掛載provide
到實(shí)例上的provides
屬性對(duì)象上,子組件獲取到自身和父組件實(shí)例,然后獲取對(duì)應(yīng)的屬性值
2.給組件實(shí)例拓展provides
屬性,類型是對(duì)象類型
- 保存父組件提供的
provide
數(shù)據(jù) - 供子組件獲取指定數(shù)據(jù)
3.給組件實(shí)例拓展parent
屬性,用于獲取父組件實(shí)例
子組件獲取父組件實(shí)例,然后獲取對(duì)應(yīng)數(shù)據(jù)
4.創(chuàng)建并實(shí)現(xiàn)provide/Inject
函數(shù)
源碼實(shí)現(xiàn)
步驟解析
拓展實(shí)現(xiàn)getCurrentInstance,用于獲取組件實(shí)例
let currentInstance = null export function getCurrentInstance(){ return currentInstance } export function setCurrentInstance(instance){ // 方便后續(xù)跟蹤 currentInstance 被誰(shuí)更改 - 斷點(diǎn)調(diào)試 中間層概念 currentInstance = instance } export function setupComponent(instance) { // 處理setup的信息 初始化props 初始化Slots等 initProps(instance,instance.vnode.props), initSlots(instance,instance.vnode.children), setupStatefulComponent(instance); } // 調(diào)用創(chuàng)建組件實(shí)例 function setupStatefulComponent(instance: any) { // 調(diào)用組件的setup // const Component = instance.vNode.type const Component = instance.type; instance.proxy = new Proxy( { _: instance }, PublicInstanceProxyHandlers // { // get(target,key){ // const { setupState } = instance // if(key in setupState){ // return setupState[key] // } // if(key === '$el'){ // return instance.vnode.el // } // } // } ); const { setup } = Component; if (setup) { // currentInstance = instance setCurrentInstance(instance) // setup可以返回函數(shù)或?qū)ο?函數(shù)-是組件的render函數(shù) 對(duì)象-將對(duì)象返回的對(duì)象注入到這個(gè)組件上下文中 const setupResult = setup(shallowReadonly(instance.props),{ emit: instance.emit }); // currentInstance = null setCurrentInstance(null) // setup返回當(dāng)前組件的數(shù)據(jù) handleSetupResult(instance, setupResult); } }
組件實(shí)例拓展provides和parent屬性
export function createComponentInstance(vnode,parent) { const component = { vnode, type: vnode.type, props: {}, slots: {}, isMounted: false, // 標(biāo)識(shí)是否是初次加載還是后續(xù)依賴數(shù)據(jù)的更新操作 subTree: null, emit: ()=>{}, provides:{}, //常規(guī)的provide 無(wú)法實(shí)現(xiàn)跨級(jí)的父子組件provide和inject parent, //子組件獲取到父組件實(shí)例 取得父組件中 provide 的數(shù)據(jù) render: vnode.render, setupState: {}, }; component.emit = emit.bind(null,component) as any return component; }
按照createComponentInstance的新添屬性parent進(jìn)行舊邏輯兼容
function processComponent(vnode: any, container: any, parentComponent) { // 掛載組件 mountComponent(vnode, container, parentComponent); } function mountComponent(initialVNode: any, container, parentComponent) { // 通過(guò)虛擬節(jié)點(diǎn)創(chuàng)建組件實(shí)例 const insatnce = createComponentInstance(initialVNode,parentComponent); const { data } = insatnce.type // 通過(guò)data函數(shù)獲取原始數(shù)據(jù),并調(diào)用reactive函數(shù)將其包裝成響應(yīng)式數(shù)據(jù) // const state = reactive(data()) // 為了使得自身狀態(tài)值發(fā)生變化時(shí)組件可以實(shí)現(xiàn)更新操作,需要將整個(gè)渲染任務(wù)放入到Effect中進(jìn)行收集 effect(() => { setupComponent(insatnce); //處理setup的信息 初始化props 初始化Slots等 setupRenderEffect(insatnce, initialVNode, container); // 首次調(diào)用App組件時(shí)會(huì)執(zhí)行 并將render函數(shù)的this綁定為創(chuàng)建的代理對(duì)象 }) } // ... export function render(vnode, container) { // 調(diào)用patch函數(shù) 方便進(jìn)行后續(xù)遞歸處理 patch(vnode, container, null); } // ... // 省略其他兼容邏輯,如patch、mountElement、processElement、mountChildren等
檢測(cè)parent是否配置成功
export function createComponentInstance(vnode,parent) { console.log(parent,'parent=========') // ... }
實(shí)現(xiàn)provide和Inject
import { getCurrentInstance } from "./component"; export function provide (key,value){ const currentInstance:any = getCurrentInstance(); //在在setup中 if(currentInstance){ let { provides } = currentInstance provides[key] = value } } export function inject (key,defaultValue){ const currentInstance:any = getCurrentInstance(); //在在setup中 獲取當(dāng)前組件的實(shí)例 if(currentInstance){ // 獲取到父組件的實(shí)例上的provides 然后根據(jù)inject的key值進(jìn)行查找對(duì)應(yīng)的值并返回 const parentProvides = currentInstance.parent.provides return parentProvides[key] } }
跨級(jí)組件數(shù)據(jù)傳遞
在進(jìn)行provides
提供時(shí),優(yōu)先讀取父組件的provide
數(shù)據(jù)
export function createComponentInstance(vnode,parent) { console.log(parent,'parent=========') const component = { vnode, type: vnode.type, props: {}, slots: {}, isMounted: false, // 標(biāo)識(shí)是否是初次加載還是后續(xù)依賴數(shù)據(jù)的更新操作 subTree: null, emit: ()=>{}, // provides:{}, //常規(guī)的provide 無(wú)法實(shí)現(xiàn)跨級(jí)的父子組件provide和inject provides:parent?parent.provides:{}, // 實(shí)現(xiàn)跨級(jí)父子組件之間的provide和inject //相當(dāng)于是一個(gè)容器 當(dāng)調(diào)用 provide 的時(shí)候會(huì)往這個(gè)容器里存入數(shù)據(jù) 供子組件的數(shù)據(jù)讀取 parent, //子組件獲取到父組件實(shí)例 取得父組件中 provide 的數(shù)據(jù) render: vnode.render, setupState: {}, }; component.emit = emit.bind(null,component) as any return component; }
利用原型鏈思想解決父組件和爺爺組件都provide
相同key
值的數(shù)據(jù)時(shí)的覆蓋問(wèn)題 -> 只會(huì)讀到最上層
父組件沒(méi)有自己維護(hù)的provides
對(duì)象,導(dǎo)致只保存做外部的組件provides
數(shù)據(jù),so每個(gè)組件都應(yīng)該維護(hù)一個(gè)專屬于自己的provides
屬性供子組件使用,當(dāng)父組件中有值則直接返回,沒(méi)有則繼續(xù)向父組件的父組件進(jìn)行循環(huán)查找,直到到達(dá)頂層組件,頂層組件也沒(méi)有時(shí)則采用Inject
配置的默認(rèn)值進(jìn)行渲染
在進(jìn)行組件專屬provides
維護(hù)
export function provide (key,value){ const currentInstance:any = getCurrentInstance(); //在在setup中 if(currentInstance){ let { provides } = currentInstance const parentProvides = currentInstance.parent.provides // 讓當(dāng)前組件實(shí)例的provides指向一個(gè)空對(duì)象 且該對(duì)象以父組件的 provides 為原型 // currentInstance.provides = Object.create(parentProvides) // 上述注釋的邏輯存在的問(wèn)題是每次調(diào)用provide時(shí)都會(huì)將組件實(shí)例的provides置為空對(duì)象,導(dǎo)致以前提供的數(shù)據(jù)被清空 // 所以清空邏輯只適合在首次加載時(shí)進(jìn)行調(diào)用 而首次加載即是組件實(shí)例的provides是初始化父組件實(shí)例的provides 此時(shí)可以進(jìn)行初始化 if(provides === parentProvides){ provides = currentInstance.provides = Object.create(parentProvides) // Object.create可以理解為繼承一個(gè)對(duì)象,添加的屬性是在原型下 此處是將父組件的provides屬性設(shè)置到當(dāng)前組件實(shí)例對(duì)象的provides屬性的原型對(duì)象上 } provides[key] = value } }
Inject默認(rèn)值問(wèn)題
Inject函數(shù)的第二參數(shù)即為默認(rèn)值
默認(rèn)值可以是函數(shù)或者普通值
export function inject (key,defaultValue){ const currentInstance:any = getCurrentInstance(); //在在setup中 獲取當(dāng)前組件的實(shí)例 if(currentInstance){ // 獲取到父組件的實(shí)例上的provides 然后根據(jù)inject的key值進(jìn)行查找對(duì)應(yīng)的值并返回 const parentProvides = currentInstance.parent.provides if(key in parentProvides){ return parentProvides[key] } else if(defaultValue){ // 支持inject的默認(rèn)值 當(dāng)父組件中沒(méi)有提供數(shù)據(jù)時(shí)進(jìn)行采取默認(rèn)值 默認(rèn)值可以是一個(gè)函數(shù)或者普通值 if(typeof defaultValue === 'function'){ return defaultValue() } return defaultValue } } }
測(cè)試用例
// 省略父組件邏輯... const Consumer = { name: "Consumer", setup() { const foo = inject("foo"); const bar = inject("bar"); const bars = inject("bars",'bares default'); const barfn = inject("barss",() => 'bares default fn'); return { foo, bar, bars, barfn } }, render(){ return h("div",{},`Consumer: - ${this.foo} - ${this.bar} - ${this.bars} - ${this.barfn}`) } }
拓展
實(shí)現(xiàn)原理是利用了原型和原型鏈來(lái)進(jìn)行數(shù)據(jù)的繼承和獲取
原型和原型鏈
prototype和__proto__
prototype
一般是顯式原型,__proto__
一般稱為隱式原型,一個(gè)函數(shù)在創(chuàng)建之后,就會(huì)擁有一個(gè)名為prototype
的屬性,這個(gè)屬性表示函數(shù)的原型對(duì)象;
原型鏈
當(dāng)訪問(wèn)一個(gè)JS對(duì)象屬性的時(shí)候,JS會(huì)先在這個(gè)對(duì)象定義的屬性上查找,找不到會(huì)沿著這個(gè)對(duì)象的__proto__
這個(gè)隱式原型關(guān)聯(lián)起來(lái)的鏈條向上一個(gè)對(duì)象查找,這個(gè)鏈條就叫做原型鏈;
原型鏈在某種意義上是讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法
以上就是Vue3實(shí)現(xiàn)provide/inject的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue3 provide inject的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何測(cè)量vue應(yīng)用運(yùn)行時(shí)的性能
這篇文章主要介紹了如何測(cè)量vue應(yīng)用運(yùn)行時(shí)的性能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06vue實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)和參數(shù)傳遞的兩種方式
這篇文章主要介紹了vue頁(yè)面跳轉(zhuǎn)和參數(shù)傳遞的兩種方式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09Vue中使用ECharts與v-if的問(wèn)題和解決方案
在Vue項(xiàng)目中使用v-if指令控制ECharts圖表顯示時(shí),可能會(huì)遇到圖表無(wú)法正常渲染或顯示錯(cuò)誤的問(wèn)題,下面這篇文章主要介紹了Vue中使用ECharts與v-if的問(wèn)題和解決方案,需要的朋友可以參考下2024-10-10Vue3中reactive函數(shù)toRef函數(shù)ref函數(shù)簡(jiǎn)介
這篇文章主要介紹了Vue3中的三種函數(shù),分別對(duì)reactive函數(shù)toRef函數(shù)以及ref函數(shù)原理及使用作了簡(jiǎn)單介紹,有需要的朋友可以借鑒參考下2021-09-09使用FileReader API創(chuàng)建Vue文件閱讀器組件
這篇文章主要介紹了使用FileReader API創(chuàng)建一個(gè)Vue的文件閱讀器組件,需要的朋友可以參考下2018-04-04vue中created、watch和computed的執(zhí)行順序詳解
由于vue的雙向數(shù)據(jù)綁定,自動(dòng)更新數(shù)據(jù)的機(jī)制,在數(shù)據(jù)變化后,對(duì)此數(shù)據(jù)依賴?的所有數(shù)據(jù),watch事件都會(huì)被更新、觸發(fā),下面這篇文章主要給大家介紹了關(guān)于vue中created、watch和computed的執(zhí)行順序,需要的朋友可以參考下2022-11-11vue中g(shù)et請(qǐng)求如何傳遞數(shù)組參數(shù)的方法示例
這篇文章主要介紹了vue中g(shù)et請(qǐng)求如何傳遞數(shù)組參數(shù)的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11