Vue3實(shí)現(xiàn)provide/inject的示例詳解
實(shí)現(xiàn)思路
場景分析
可以在全局父組件里通過provide將所有需要對外提供的全局屬性方法進(jìn)行跨組件透傳,無論嵌套多深的子組件都可以進(jìn)行inject注入使用,包括不限于計(jì)算屬性、方法等,甚至將整個(gè)app.vue實(shí)例進(jìn)行相應(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é)果

步驟解析
當(dāng)調(diào)用provide的時(shí)候需要將provide中的key/value掛載到組件實(shí)例的provides屬性上,當(dāng)子組件調(diào)用Inject的時(shí)候,通過獲取到子組件的實(shí)例進(jìn)而通過parent得到父組件實(shí)例,然后通過父組件實(shí)例上的provides對象獲取到相應(yīng)key對應(yīng)的value
1.實(shí)現(xiàn)getCurrentInstance函數(shù)
用于獲取組件實(shí)例實(shí)例,父組件掛載provide到實(shí)例上的provides屬性對象上,子組件獲取到自身和父組件實(shí)例,然后獲取對應(yīng)的屬性值
2.給組件實(shí)例拓展provides屬性,類型是對象類型
- 保存父組件提供的
provide數(shù)據(jù) - 供子組件獲取指定數(shù)據(jù)
3.給組件實(shí)例拓展parent屬性,用于獲取父組件實(shí)例
子組件獲取父組件實(shí)例,然后獲取對應(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 被誰更改 - 斷點(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ù) 對象-將對象返回的對象注入到這個(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)識是否是初次加載還是后續(xù)依賴數(shù)據(jù)的更新操作
subTree: null,
emit: ()=>{},
provides:{}, //常規(guī)的provide 無法實(shí)現(xiàn)跨級的父子組件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) {
// 通過虛擬節(jié)點(diǎn)創(chuàng)建組件實(shí)例
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ā)生變化時(shí)組件可以實(shí)現(xiàn)更新操作,需要將整個(gè)渲染任務(wù)放入到Effect中進(jìn)行收集
effect(() => {
setupComponent(insatnce); //處理setup的信息 初始化props 初始化Slots等
setupRenderEffect(insatnce, initialVNode, container); // 首次調(diào)用App組件時(shí)會執(zhí)行 并將render函數(shù)的this綁定為創(chuàng)建的代理對象
})
}
// ...
export function render(vnode, container) {
// 調(diào)用patch函數(shù) 方便進(jìn)行后續(xù)遞歸處理
patch(vnode, container, null);
}
// ...
// 省略其他兼容邏輯,如patch、mountElement、processElement、mountChildren等
檢測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)行查找對應(yīng)的值并返回
const parentProvides = currentInstance.parent.provides
return parentProvides[key]
}
}跨級組件數(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)識是否是初次加載還是后續(xù)依賴數(shù)據(jù)的更新操作
subTree: null,
emit: ()=>{},
// provides:{}, //常規(guī)的provide 無法實(shí)現(xiàn)跨級的父子組件provide和inject
provides:parent?parent.provides:{},
// 實(shí)現(xiàn)跨級父子組件之間的provide和inject
//相當(dāng)于是一個(gè)容器 當(dāng)調(diào)用 provide 的時(shí)候會往這個(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í)的覆蓋問題 -> 只會讀到最上層
父組件沒有自己維護(hù)的provides對象,導(dǎo)致只保存做外部的組件provides數(shù)據(jù),so每個(gè)組件都應(yīng)該維護(hù)一個(gè)專屬于自己的provides屬性供子組件使用,當(dāng)父組件中有值則直接返回,沒有則繼續(xù)向父組件的父組件進(jìn)行循環(huán)查找,直到到達(dá)頂層組件,頂層組件也沒有時(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è)空對象 且該對象以父組件的 provides 為原型
// currentInstance.provides = Object.create(parentProvides)
// 上述注釋的邏輯存在的問題是每次調(diào)用provide時(shí)都會將組件實(shí)例的provides置為空對象,導(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è)對象,添加的屬性是在原型下 此處是將父組件的provides屬性設(shè)置到當(dāng)前組件實(shí)例對象的provides屬性的原型對象上
}
provides[key] = value
}
}Inject默認(rè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)行查找對應(yīng)的值并返回
const parentProvides = currentInstance.parent.provides
if(key in parentProvides){
return parentProvides[key]
} else if(defaultValue){
// 支持inject的默認(rèn)值 當(dāng)父組件中沒有提供數(shù)據(jù)時(shí)進(jìn)行采取默認(rèn)值 默認(rèn)值可以是一個(gè)函數(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}`)
}
}
拓展
實(shí)現(xiàn)原理是利用了原型和原型鏈來進(jìn)行數(shù)據(jù)的繼承和獲取
原型和原型鏈
prototype和__proto__
prototype一般是顯式原型,__proto__一般稱為隱式原型,一個(gè)函數(shù)在創(chuàng)建之后,就會擁有一個(gè)名為prototype的屬性,這個(gè)屬性表示函數(shù)的原型對象;
原型鏈
當(dāng)訪問一個(gè)JS對象屬性的時(shí)候,JS會先在這個(gè)對象定義的屬性上查找,找不到會沿著這個(gè)對象的__proto__這個(gè)隱式原型關(guān)聯(lián)起來的鏈條向上一個(gè)對象查找,這個(gè)鏈條就叫做原型鏈;
原型鏈在某種意義上是讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法
以上就是Vue3實(shí)現(xiàn)provide/inject的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue3 provide inject的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3模塊創(chuàng)建runtime-dom源碼解析
這篇文章主要為大家介紹了vue3模塊創(chuàng)建runtime-dom源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
vue 基于element-ui 分頁組件封裝的實(shí)例代碼
這篇文章主要介紹了vue 基于element-ui 分頁組件封裝的實(shí)例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-12-12
詳解基于Vue-cli搭建的項(xiàng)目如何和后臺交互
這篇文章主要介紹了詳解基于Vue-cli搭建的項(xiàng)目如何和后臺交互,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06

