基础版
- 实现 provide/inject 声明
- 实现父子组件的注入/取值
案例
/*
* @Author: Lin zefan
* @Date: 2022-03-21 21:46:14
* @LastEditTime: 2022-03-31 18:33:22
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\example\provide-inject\App.js
*
*/
import { h, provide, inject } from "../../lib/mini-vue.esm.js";
export default {
name: "Provider",
render() {
return h("div", {}, [h("div", {}, "Provider"), h(Consumer)]);
},
setup() {
// 在上层 provide
provide("foo", "foo");
},
};
const Consumer = {
name: "Consumer",
render() {
return h("div", {}, "Consumer: " + `inject foo: ${this.foo}`);
},
setup() {
return {
// 在下层 inject
foo: inject("foo"),
};
},
};
实现
创建provide/inject
apiInject.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-31 18:34:20
* @LastEditTime: 2022-04-01 11:37:02
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\runtime-core\apiInject.ts
*
*/
import { getCurrentInstance } from "./component";
export function provide(key, value) {
const currentInstance = getCurrentInstance() as any;
if (currentInstance) {
const { providers } = currentInstance;
providers[key] = value;
}
}
export function inject(key) {
const currentInstance = getCurrentInstance() as any;
if (currentInstance) {
/**
* 1. 获取的是父组件的providers,而不是自身
* 2. 所以我们需要把父组件实例注入到实例对象
*/
const { providers } = currentInstance.parent;
return providers[key];
}
}
拓展实例对象
component.ts
/*
* @Author: Lin zefan
* @Date: 2022-03-21 22:08:11
* @LastEditTime: 2022-04-01 11:36:28
* @LastEditors: Lin zefan
* @Description: 处理组件类型
* @FilePath: \mini-vue3\src\runtime-core\component.ts
*
*/
// 初始化Component结构
function createComponentInstance(initVNode, parent) {
const component = {
vnode: initVNode,
type: initVNode.type,
proxy: null,
setupState: {},
props: {},
slots: {},
providers: {},
emit: () => {},
// 挂载父组件实例
parent,
};
/** 注册emit
* 1. 通过bind把当前实例给到emit函数
*/
component.emit = emit.bind(null, component) as any;
return component;
}
createComponentInstance 新增了 parent 形参,接收的是父组件实例,其他相关函数也要把 parent 传递过来
components.ts
function processComponent(vnode, container, parentComponent)
function mountComponent(vnode, container, parentComponent)
// 这里是最关键的一部分,会通过patch把父级实例传递下去
function setupRenderEffect(instance, container) {
const { proxy, vnode } = instance;
// 通过render函数,获取render返回虚拟节点,并绑定render的this
const subTree = instance.render.call(proxy);
/**
* 1. 调用组件render后把结果再次给到patch
* 2. 再把对应的dom节点append到container
* 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
*/
patch(subTree, container, instance);
}
render.ts
export function patch(vnode, container, parentComponent) {
if (!vnode) return;
const { type } = vnode;
switch (type) {
case Fragment:
processFragment(vnode, container, parentComponent);
break;
case TextNode:
processTextNode(vnode, container);
break;
default:
if (isObject(type)) {
// 是一个Component
processComponent(vnode, container, parentComponent);
} else if (typeof type === "string") {
// 是一个element
processElement(vnode, container, parentComponent);
}
break;
}
}
element.ts
function processElement(vnode, container, parentComponent)
function mountElement(vnode, container, parentComponent)
function mountChildren(children, container, parentComponent)
function processFragment(vnode: any, container: any, parentComponent)
升级版
- provide可深层注入,子组件会依次往上级组件查找
例子
App.js
/*
* @Author: Lin zefan
* @Date: 2022-03-21 21:46:14
* @LastEditTime: 2022-04-01 12:11:58
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\example\provide-inject\App.js
*
*/
import { h, provide, inject } from "../../lib/mini-vue.esm.js";
export default {
name: "Provider",
render() {
return h("div", {}, [h("div", {}, "Provider"), h(Provider2)]);
},
setup() {
// 在上层 provide
provide("foo", "foo");
},
};
const Provider2 = {
render() {
return h("div", {}, [h("div", {}, `Provider2:${this.foo}`), h(Provider3)]);
},
setup() {
provide("foo", "foo2");
return {
// 在下层 inject
foo: inject("foo"),
};
},
};
const Provider3 = {
render() {
return h("div", {}, [h("div", {}, `Provider3:${this.foo}`), h(Consumer)]);
},
setup() {
provide("foo", "foo3");
return {
// 在下层 inject
foo: inject("foo"),
};
},
};
const Consumer = {
name: "Consumer",
render() {
return h("div", {}, "Consumer: " + `inject foo: ${this.foo}`);
},
setup() {
return {
// 在下层 inject
foo: inject("foo"),
};
},
};
简单版
指向父级providers
function createComponentInstance(initVNode, parent) {
const component = {
vnode: initVNode,
type: initVNode.type,
proxy: null,
setupState: {},
props: {},
slots: {},
/** 当前的 providers 指向父级的 providers,解决跨层取值,但是有缺陷
* 1. 引用的关系会影响父组件,当子组件注入同名的foo,就会影响到父组件的foo
* const father = { foo:1};
const children = father;
children.foo =2;
console.log(father, children)
*/
providers: parent ? parent.providers : {},
emit: () => {},
// 挂载父组件实例
parent,
};
/** 注册emit
* 1. 通过bind把当前实例给到emit函数
*/
component.emit = emit.bind(null, component) as any;
return component;
}
这个方案是有缺陷的,由于引用的关系会影响父组件,当子组件注入同名的foo,就会影响到父组件的foo,导致父组件的值也被修改。
优化版
借助 Object.create 去创建对应的 prototype
/*
* @Author: Lin zefan
* @Date: 2022-03-31 18:34:20
* @LastEditTime: 2022-04-01 12:50:13
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\src\runtime-core\apiInject.ts
*
*/
import { getCurrentInstance } from "./component";
export function provide(key, value) {
const currentInstance = getCurrentInstance() as any;
if (currentInstance) {
let { providers } = currentInstance;
const parentProviders =
currentInstance.parent && currentInstance.parent.providers;
/** 初始化判断
* 1. 根组件没有parent,这个判断不会走
* 2. 判断当前providers与父级providers是否相等,相等即初始化
*/
if (providers === parentProviders) {
/** 初始化组件providers
* 1. 通过Object.create创建一个新对象,避免引用导致的问题
* 2. 通过Object.create传入父组件数据,Object.create内部会挂载prototype
* 3. 当前组件获取不到数据,可以通过prototype向上级寻找(原型链)
*/
providers = currentInstance.providers = Object.create(parentProviders);
}
providers[key] = value;
}
}
默认值
inject支持默认值,可以是string或者函数
// App.js
/*
* @Author: Lin zefan
* @Date: 2022-03-21 21:46:14
* @LastEditTime: 2022-04-01 13:48:26
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\example\provide-inject\App.js
*
*/
import { h, provide, inject } from "../../lib/mini-vue.esm.js";
export default {
name: "Provider",
render() {
return h("div", {}, [h("div", {}, "Provider"), h(Provider2)]);
},
setup() {
// 在上层 provide
provide("foo", "foo");
},
};
const Provider2 = {
name: "Provider2",
render() {
return h("div", {}, [h("div", {}, `Provider2:${this.foo}`), h(Provider3)]);
},
setup() {
provide("foo", "foo2");
return {
// 在下层 inject
foo: inject("foo"),
};
},
};
const Provider3 = {
name: "Provider3",
render() {
return h("div", {}, [
h("div", {}, `Provider3:${this.foo}`),
h("div", {}, `Provider3-baseFoo:${this.baseFoo}`),
h("div", {}, `Provider3-baseBar:${this.baseBar}`),
h(Consumer),
]);
},
setup() {
provide("foo", "foo3");
const baseFoo = inject("baseFoo", "base");
const baseBar = inject("baseBar", () => "bar");
return {
// 在下层 inject
foo: inject("foo"),
baseFoo,
baseBar,
};
},
};
const Consumer = {
name: "Consumer",
render() {
return h("div", {}, "Consumer: " + `inject foo: ${this.foo}`);
},
setup() {
return {
// 在下层 inject
foo: inject("foo"),
};
},
};
// apiInject.ts
export function inject(key, defaultVal) {
const currentInstance = getCurrentInstance() as any;
if (currentInstance) {
/**
* 1. 获取的是父元素的providers,而不是自身
* 2. 所以我们需要把父组件实例注入到实例对象
*/
const { providers } = currentInstance.parent;
// 支持默认值,string | array
if (!providers[key] && defaultVal) {
if (typeof defaultVal === "function") {
return defaultVal();
}
return defaultVal;
}
return providers[key];
}
}