vue2.0源码解析,initInjections与initProvide

前言

在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.ownKeysObject.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.definePropertyget时,当前Dep.targetrender或其他的wacther,能够收集到引用的对象的值。

// 上层组件
data() {
    return {
         obj: {
             text: 'ObjText-old'
         }
     }
 },
 provide() {
     return {
         foo: this.obj
     }
 },

// 下层组件接收
inject: {
  foo: {
      default: () => {
           return {
               text: 'ObjText-children'
           }
       }
   	}
},

到这里initInjections与initProvide基本已经解释完毕,已经将我看源码时的疑问写在上面,如有其他疑问不明白,可留言或私信,我将补充到博客中。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值