Vue3實現(xiàn)provide/inject的示例詳解
實現(xiàn)思路
場景分析
可以在全局父組件里通過provide
將所有需要對外提供的全局屬性方法進行跨組件透傳,無論嵌套多深的子組件都可以進行inject
注入使用,包括不限于計算屬性、方法等,甚至將整個app.vue實例進行相應(yīng)的透傳;
測試用例
// 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é)果
步驟解析
當調(diào)用provide
的時候需要將provide
中的key/value
掛載到組件實例的provides
屬性上,當子組件調(diào)用Inject
的時候,通過獲取到子組件的實例進而通過parent
得到父組件實例,然后通過父組件實例上的provides
對象獲取到相應(yīng)key
對應(yīng)的value
1.實現(xiàn)getCurrentInstance
函數(shù)
用于獲取組件實例實例,父組件掛載provide
到實例上的provides
屬性對象上,子組件獲取到自身和父組件實例,然后獲取對應(yīng)的屬性值
2.給組件實例拓展provides
屬性,類型是對象類型
- 保存父組件提供的
provide
數(shù)據(jù) - 供子組件獲取指定數(shù)據(jù)
3.給組件實例拓展parent
屬性,用于獲取父組件實例
子組件獲取父組件實例,然后獲取對應(yīng)數(shù)據(jù)
4.創(chuàng)建并實現(xiàn)provide/Inject
函數(shù)
源碼實現(xiàn)
步驟解析
拓展實現(xiàn)getCurrentInstance,用于獲取組件實例
let currentInstance = null export function getCurrentInstance(){ return currentInstance } export function setCurrentInstance(instance){ // 方便后續(xù)跟蹤 currentInstance 被誰更改 - 斷點調(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)建組件實例 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ù) 對象-將對象返回的對象注入到這個組件上下文中 const setupResult = setup(shallowReadonly(instance.props),{ emit: instance.emit }); // currentInstance = null setCurrentInstance(null) // setup返回當前組件的數(shù)據(jù) handleSetupResult(instance, setupResult); } }
組件實例拓展provides和parent屬性
export function createComponentInstance(vnode,parent) { const component = { vnode, type: vnode.type, props: {}, slots: {}, isMounted: false, // 標識是否是初次加載還是后續(xù)依賴數(shù)據(jù)的更新操作 subTree: null, emit: ()=>{}, provides:{}, //常規(guī)的provide 無法實現(xiàn)跨級的父子組件provide和inject parent, //子組件獲取到父組件實例 取得父組件中 provide 的數(shù)據(jù) render: vnode.render, setupState: {}, }; component.emit = emit.bind(null,component) as any return component; }
按照createComponentInstance的新添屬性parent進行舊邏輯兼容
function processComponent(vnode: any, container: any, parentComponent) { // 掛載組件 mountComponent(vnode, container, parentComponent); } function mountComponent(initialVNode: any, container, parentComponent) { // 通過虛擬節(jié)點創(chuàng)建組件實例 const insatnce = createComponentInstance(initialVNode,parentComponent); const { data } = insatnce.type // 通過data函數(shù)獲取原始數(shù)據(jù),并調(diào)用reactive函數(shù)將其包裝成響應(yīng)式數(shù)據(jù) // const state = reactive(data()) // 為了使得自身狀態(tài)值發(fā)生變化時組件可以實現(xiàn)更新操作,需要將整個渲染任務(wù)放入到Effect中進行收集 effect(() => { setupComponent(insatnce); //處理setup的信息 初始化props 初始化Slots等 setupRenderEffect(insatnce, initialVNode, container); // 首次調(diào)用App組件時會執(zhí)行 并將render函數(shù)的this綁定為創(chuàng)建的代理對象 }) } // ... export function render(vnode, container) { // 調(diào)用patch函數(shù) 方便進行后續(xù)遞歸處理 patch(vnode, container, null); } // ... // 省略其他兼容邏輯,如patch、mountElement、processElement、mountChildren等
檢測parent是否配置成功
export function createComponentInstance(vnode,parent) { console.log(parent,'parent=========') // ... }
實現(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中 獲取當前組件的實例 if(currentInstance){ // 獲取到父組件的實例上的provides 然后根據(jù)inject的key值進行查找對應(yīng)的值并返回 const parentProvides = currentInstance.parent.provides return parentProvides[key] } }
跨級組件數(shù)據(jù)傳遞
在進行provides
提供時,優(yōu)先讀取父組件的provide
數(shù)據(jù)
export function createComponentInstance(vnode,parent) { console.log(parent,'parent=========') const component = { vnode, type: vnode.type, props: {}, slots: {}, isMounted: false, // 標識是否是初次加載還是后續(xù)依賴數(shù)據(jù)的更新操作 subTree: null, emit: ()=>{}, // provides:{}, //常規(guī)的provide 無法實現(xiàn)跨級的父子組件provide和inject provides:parent?parent.provides:{}, // 實現(xiàn)跨級父子組件之間的provide和inject //相當于是一個容器 當調(diào)用 provide 的時候會往這個容器里存入數(shù)據(jù) 供子組件的數(shù)據(jù)讀取 parent, //子組件獲取到父組件實例 取得父組件中 provide 的數(shù)據(jù) render: vnode.render, setupState: {}, }; component.emit = emit.bind(null,component) as any return component; }
利用原型鏈思想解決父組件和爺爺組件都provide
相同key
值的數(shù)據(jù)時的覆蓋問題 -> 只會讀到最上層
父組件沒有自己維護的provides
對象,導(dǎo)致只保存做外部的組件provides
數(shù)據(jù),so每個組件都應(yīng)該維護一個專屬于自己的provides
屬性供子組件使用,當父組件中有值則直接返回,沒有則繼續(xù)向父組件的父組件進行循環(huán)查找,直到到達頂層組件,頂層組件也沒有時則采用Inject
配置的默認值進行渲染
在進行組件專屬provides
維護
export function provide (key,value){ const currentInstance:any = getCurrentInstance(); //在在setup中 if(currentInstance){ let { provides } = currentInstance const parentProvides = currentInstance.parent.provides // 讓當前組件實例的provides指向一個空對象 且該對象以父組件的 provides 為原型 // currentInstance.provides = Object.create(parentProvides) // 上述注釋的邏輯存在的問題是每次調(diào)用provide時都會將組件實例的provides置為空對象,導(dǎo)致以前提供的數(shù)據(jù)被清空 // 所以清空邏輯只適合在首次加載時進行調(diào)用 而首次加載即是組件實例的provides是初始化父組件實例的provides 此時可以進行初始化 if(provides === parentProvides){ provides = currentInstance.provides = Object.create(parentProvides) // Object.create可以理解為繼承一個對象,添加的屬性是在原型下 此處是將父組件的provides屬性設(shè)置到當前組件實例對象的provides屬性的原型對象上 } provides[key] = value } }
Inject默認值問題
Inject函數(shù)的第二參數(shù)即為默認值
默認值可以是函數(shù)或者普通值
export function inject (key,defaultValue){ const currentInstance:any = getCurrentInstance(); //在在setup中 獲取當前組件的實例 if(currentInstance){ // 獲取到父組件的實例上的provides 然后根據(jù)inject的key值進行查找對應(yīng)的值并返回 const parentProvides = currentInstance.parent.provides if(key in parentProvides){ return parentProvides[key] } else if(defaultValue){ // 支持inject的默認值 當父組件中沒有提供數(shù)據(jù)時進行采取默認值 默認值可以是一個函數(shù)或者普通值 if(typeof defaultValue === 'function'){ return defaultValue() } return defaultValue } } }
測試用例
// 省略父組件邏輯... 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}`) } }
拓展
實現(xiàn)原理是利用了原型和原型鏈來進行數(shù)據(jù)的繼承和獲取
原型和原型鏈
prototype和__proto__
prototype
一般是顯式原型,__proto__
一般稱為隱式原型,一個函數(shù)在創(chuàng)建之后,就會擁有一個名為prototype
的屬性,這個屬性表示函數(shù)的原型對象;
原型鏈
當訪問一個JS對象屬性的時候,JS會先在這個對象定義的屬性上查找,找不到會沿著這個對象的__proto__
這個隱式原型關(guān)聯(lián)起來的鏈條向上一個對象查找,這個鏈條就叫做原型鏈;
原型鏈在某種意義上是讓一個引用類型繼承另一個引用類型的屬性和方法
以上就是Vue3實現(xiàn)provide/inject的示例詳解的詳細內(nèi)容,更多關(guān)于Vue3 provide inject的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue實現(xiàn)頁面跳轉(zhuǎn)和參數(shù)傳遞的兩種方式
這篇文章主要介紹了vue頁面跳轉(zhuǎn)和參數(shù)傳遞的兩種方式,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09Vue3中reactive函數(shù)toRef函數(shù)ref函數(shù)簡介
這篇文章主要介紹了Vue3中的三種函數(shù),分別對reactive函數(shù)toRef函數(shù)以及ref函數(shù)原理及使用作了簡單介紹,有需要的朋友可以借鑒參考下2021-09-09使用FileReader API創(chuàng)建Vue文件閱讀器組件
這篇文章主要介紹了使用FileReader API創(chuàng)建一個Vue的文件閱讀器組件,需要的朋友可以參考下2018-04-04vue中created、watch和computed的執(zhí)行順序詳解
由于vue的雙向數(shù)據(jù)綁定,自動更新數(shù)據(jù)的機制,在數(shù)據(jù)變化后,對此數(shù)據(jù)依賴?的所有數(shù)據(jù),watch事件都會被更新、觸發(fā),下面這篇文章主要給大家介紹了關(guān)于vue中created、watch和computed的執(zhí)行順序,需要的朋友可以參考下2022-11-11vue中g(shù)et請求如何傳遞數(shù)組參數(shù)的方法示例
這篇文章主要介紹了vue中g(shù)et請求如何傳遞數(shù)組參數(shù)的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11