前言
在vue 2.2之后,新增了**(provide / inject) API**,用于跨层级的数据传递。
vue官方文档 - provide / inject
安定针: provide / inject的源码非常简单
下面 (provide / inject) API 的使用方式
// 父级组件提供 'foo'
// 方式 1 直接传值
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 方式 2
// 函数式返回值,可变为响应式
provide: () => {
return {
foo: this.***
}
},
// 子组件注入 'foo'
// 方式 1
var Child = {
inject: ['foo'],
}
// 方式 2
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
在init阶段inject 是比 provide更早,比initState(initProps、initMethods、initComputed、initWatch) 都要早,因为vue的组件层级创建父组件created后再去创建子组件,一层一层向下创建的模式,那么inject如果有在上级组件定义provide,那么都会拿得到,而methods、computed、watch也有可能会用到 inject的值,所以需要放在最先初始化。
下面正式开始源码部分
其余vue源码解析部分,
data导航:vue2.0源码解析,Data
methods导航:vue2.0源码解析,Methods
initRender导航: vue2.0源码解析,initRender
initProps导航: vue2.0源码解析,initState之initProps
initComputed、initWatch导航: vue2.0源码解析,initState之initProps
initProvide
export function initProvide(vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function' ?
provide.call(vm) :
provide
}
}
你没看错,initProvide的代码只有这么多 ,前面介绍过可以传入函数或对象,那么此处只是做了一个判断,函数则运行得到return的值,并赋值到vm._provided。
initInjections
export function initInjections(vm: Component) {
const result = resolveInject(vm.$options.inject, vm);
if (result) {
Object.keys(result).forEach((key) => {
/* istanbul ignore else */
/*为对象defineProperty上在变化时通知的属性*/
// 添加数据响应式
defineReactive(vm, key, result[key]);
});
}
}
在函数一开始就调用了resolveInject处理vm.$options.inject,我们先看下resolveInject方法。
// 向上级查找对应的provided,找到每个inject里面的值,然后返回
export function resolveInject(inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
// isArray here
const isArray = Array.isArray(inject);
const result = Object.create(null);
// 浏览器是否支持原生Symbol方法,代表支持es6原生
const objectKey = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);
const keys = isArray ? inject : objectKey;
// 循环keys,一层层的向上查找,直到找到所有的inject里面的key
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const provideKey = isArray ? key : inject[key];
let source = vm;
// 向上级查找对应的provided
while (source) {
if (source._provided && provideKey in source._provided) {
result[key] = source._provided[provideKey];
break;
}
// 找不到source等于父级,继续查找,直到root
source = source.$parent;
}
// 如果父级没有找到,赋值default
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default;
result[key] = typeof provideDefault === 'function' ?
provideDefault.call(vm) :
provideDefault
}
}
}
return result;
}
}
这里的Reflect.ownKeys与Object.keys的区别就是可以返回symbol的Key,但需要在高版本浏览器上支持,所以这里做了降级兼容。
MDN文档 - Reflect.ownKeys()
其实resolveInject方法的核心作用就是向上级查找对应的provided,
首先根据传入的 inject 为数组或对象使用不同方法拿到key,
const isArray = Array.isArray(inject);
const result = Object.create(null);
// 浏览器是否支持原生Symbol方法,代表支持es6原生
const objectKey = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);
const keys = isArray ? inject : objectKey;
接下来就是循环拿到的key向上级查找组件的provide,如果没有找到就会一直找到root。
while (source) {
if (source._provided && provideKey in source._provided) {
result[key] = source._provided[provideKey];
break;
}
// 找不到source等于父级,继续查找,直到root
source = source.$parent;
}
如果查找了最上级组件(root),还没有拿到provide,则会去查看inject本身是否有定义default字段,如果default字段是一个函数,则运行并将this置为当前组件vm,否则直接赋值。
// 如果父级没有找到,赋值default
if(!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default;
result[key] = typeof provideDefault === 'function' ?
provideDefault.call(vm) : provideDefault
}
}
回到initInjections,此时我们已经拿到完整的返回值,还需要将拿到的值经过defineReactive,然后绑定到vm对象下,这样我们就可以通过 **this.****拿到值,整个初始化过程就完成了。
关于响应式数据
provide里的值如果引用了data的数据,传的是基本类型,data被改变了,inject里的值是不会被改变的,因为在初始化的时候,没有监听数据。
如果需要父的数据改变了子组件的inject数据一起更新,需要传入引用类型,这样在Object.defineProperty的get时,当前Dep.target为render或其他的wacther,能够收集到引用的对象的值。
// 上层组件
data() {
return {
obj: {
text: 'ObjText-old'
}
}
},
provide() {
return {
foo: this.obj
}
},
// 下层组件接收
inject: {
foo: {
default: () => {
return {
text: 'ObjText-children'
}
}
}
},
到这里initInjections与initProvide基本已经解释完毕,已经将我看源码时的疑问写在上面,如有其他疑问不明白,可留言或私信,我将补充到博客中。