关于vue2的inject和provide官方是这么说的
1、provide和inject需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖(provide),不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
2、provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key(但是只在原生支持Symbol和 Reflect.ownKeys 的环境下可工作)
3、inject 选项应该返回一个数组或者对象
其中数组是字符串数组 如果是对象,对象的属性可以直接是本地绑定名,或者是一个对象,该对象可以有两个属性:
from:这个是provide提供的注入属性名
default:默认值
关于inject和provide源码:
function mergeOptions(parent, child, vm) {
...
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
...
return options
}
Vue.prototype._init = function(options) {
if (options && options._isComponent) {
...
} else {
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
}
...
callHook(vm, 'beforeCreate');
initInjections(vm);
// resolve injections before data/props
initState(vm);
initProvide(vm);
// resolve provide after data/props
callHook(vm, 'created');
...
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
可以看到初始化时,依赖注入初始化有三步:
- 规范inject,normalizeInject
- 初始化inject,initInjections
- 初始化provide**,initProvide
1、normalizeInject源码解析
/**
* Normalize all injections into Object-based format
*/
function normalizeInject(options, vm) {
var inject = options.inject;
if (!inject) {
return
}
var normalized = options.inject = {};
if (Array.isArray(inject)) {
for (var i = 0; i < inject.length; i++) {
normalized[inject[i]] = {
from: inject[i]
};
}
} else if (isPlainObject(inject)) {
for (var key in inject) {
var val = inject[key];
normalized[key] = isPlainObject(val) ? extend({
from: key
}, val) : {
from: val
};
}
} else {
warn("Invalid value for option \"inject\": expected an Array or an Object, " + "but got " + (toRawType(inject)) + ".", vm);
}
}
结论:
- inject应该为数组或者对象
- inject的每一个注入属性被规范化为对象,对象内有一个属性from,用于存储注入的属性来源
2、initInjections源码解析,重点在于resolveInject方法
function initInjections(vm) {
var result = resolveInject(vm.$options.inject, vm);
if (result) {
toggleObserving(false);
Object.keys(result).forEach(function(key) {
/* istanbul ignore else */
{
defineReactive$$1(vm, key, result[key], function() {
warn("Avoid mutating an injected value directly since the changes will be " + "overwritten whenever the provided component re-renders. " + "injection being mutated: \"" + key + "\"", vm);
});
}
});
toggleObserving(true);
}
}
function resolveInject(inject, vm) {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
var result = Object.create(null);
var keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// #6574 in case the inject object is observed...
if (key === '__ob__') {
continue
}
var provideKey = inject[key].from;
var source = vm;
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey];
break
}
source = source.$parent;
}
if (!source) {
if ('default'in inject[key]) {
var provideDefault = inject[key].default;
result[key] = typeof provideDefault === 'function' ? provideDefault.call(vm) : provideDefault;
} else {
warn(("Injection \"" + key + "\" not found"), vm);
}
}
}
return result
}
}
结论:
1、使用Reflect.ownKeys获取inject属性,是因为inject属性有可能是一个Symbol
2、依赖注入的属性应该在祖先组件中提供,因为会到祖先中寻找。这段代码也解释了initInjections在initProvide之前执行,因为在本身组件依赖注入(provide并inject)是没有意义的,还不如定义data。
3、再次证明from属性就是真正的注入属性名
4、如果所有祖先都没有该属性,说明没有注入;那么再次获取default属性,也就是默认值;如果也没有默认值,就报错了
3、initProvide源码解析
function initProvide(vm) {
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function' ? provide.call(vm) : provide;
}
}
结论: 如果注入的是函数就直接执行,如果不是,就直接返回provide对象; 注意:函数必须返回一个对象。
以上就是一个vue组件初始化依赖注入的过程,但是如果还有子组件,怎么办呢?接下来简单说一下子组件是如何初始化依赖注入的。 大家都知道,vue渲染页面有以下几个过程:
- 把vue template(模板)生成渲染函数;
- 再把渲染函数生成VNode(虚拟DOM)
- 最后把VNode转换成真实的DOM渲染出来。
而初始化子组件的操作就在3步骤里面。源码如下:
function createComponent(Ctor, data, context, children, tag) {
if (isUndef(Ctor)) {
return
}
var baseCtor = context.$options._base;
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
...
return vnode
}
Vue.extend = function(extendOptions) {
extendOptions = extendOptions || {};
var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
var name = extendOptions.name || Super.options.name;
if (name) {
validateComponentName(name);
}
var Sub = function VueComponent(options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(Super.options, extendOptions);
Sub['super'] = Super;
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps$1(Sub);
}
if (Sub.options.computed) {
initComputed$1(Sub);
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function(type) {
Sub[type] = Super[type];
});
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub;
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
cachedCtors[SuperId] = Sub;
return Sub
}
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Super代表Vue构造函数,这两句是构造子组件构造函数继承自Super,也就是Vue。 子组件初始化就会调用Sub里面的_init,也就是Vue里面的_init函数,该函数就有依赖注入的初始化,完毕。