系列文章目录
文章目录
一、provide/inject的作用及使用方式
依赖注入的主要只用是组件之间的传值。那相对于其他方式的特点:
优点:
- 祖先组件不需要知道哪些后代组件使用它提供的属性;
- 后代组件不需要知道被注入的属性来自哪里;
缺点:
- 组件间的耦合较为紧密,不易重构;
- 提供的属性是非响应式的;
使用:
父组件中提供依赖:
provide("intentId", 'er34dfbsdfrf);
在子组件中注入:
const intentId = inject("intentId");
二、顺道复习一下组件间的通讯方式吧
- 父子组件传值:
props
- 子父传值:触发自定义事件
- 兄弟组件传值:
eventBus
$parent / $children
与ref
$parent
方法是在子组件中可以直接访问该组件的父实例或组件。$children
方法是在父组件中可以直接访问子组件的实例,但是不保证子组件的顺序ref
被用来给DOM
元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM
元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例。
vuex
三、源码分析
组件实例初始化的时候会调用Vue.prototype._init
,vm._init
中在data/props
前面调用了initInjections
,在data/props
后面调用了initProvide
3.1 initInjections
function initInjections (vm) {
// 根据注册的inject,通过$parent向上查找对应的provide
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);
}
}
该方法主要做了以下两件事:
- 获取
vm.$options.inject
,通过resolveInject
方法找到对应的key
集合; - 遍历
key
集合,对其进行响应式监听;
3.2 resolveInject
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);
// 循环向上遍历,直到拿到祖先节点的provide值
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
}
}
从这个函数可以看到通过while
循环,以及source = source.$parent
找到父组件中的_provided
属性,拿到其值,也就拿到父组件提供的provide
了。所以说孙组件可以拿到父组件中的数据。接下来我们就看看这个_provided
属性。
3.4 initProvide
function initProvide (vm) {
var provide = vm.$options.provide;
if (provide) {
// 把provide值赋值给vm.provide
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
该方法单纯把组件注册的provide
值,赋值给vm._provided
,resolveInject
中有使用到。
通过打印发现vm.$options.provide
是个函数,其实是调用这个函数得到的_provided
。那么就看看这个函数。在父组件实例化时,我们也调用了mergeOptions
对父组件中的provide
属性进行了处理:
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
);
}
这个options
参数里面就包含provide
属性。看看mergeOptions
函数里面怎么对provide
进行处理的:
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
var strat = strats[key] || defaultStrat;
options[key] = strat(parent[key], child[key], vm, key);
}
我们看到了处理过程,看看strats
,
strats.provide = mergeDataOrFn;
function mergeDataOrFn (
parentVal,
childVal,
vm
) {
if (!vm) {
// in a Vue.extend merge, both should be functions
if (!childVal) {
return parentVal
}
if (!parentVal) {
return childVal
}
// when parentVal & childVal are both present,
// we need to return a function that returns the
// merged result of both functions... no need to
// check if parentVal is a function here because
// it has to be a function to pass previous merges.
return function mergedDataFn () {
return mergeData(
typeof childVal === 'function' ? childVal.call(this, this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
)
}
} else {
return function mergedInstanceDataFn () {
// instance merge
var instanceData = typeof childVal === 'function'
? childVal.call(vm, vm)
: childVal;
var defaultData = typeof parentVal === 'function'
? parentVal.call(vm, vm)
: parentVal;
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
return defaultData
}
}
}
}
通过打印我们看到了vm.$options.provide
就是mergedInstanceDataFn
函数。通过调用这个函数我们_provided
就成为了{"parentValue":"here is parent data"}
。