prop 逐级透传问题
通常情况下,当我们需要从父组建向子组件传递参数时会用到prop
。但是一个多层级嵌套的组件,会形成一个巨大的组建树,如果某一个深层的子组件需要用到某个较远的祖先组件的一些数据,仅通过props
一层一层的向下传递,会非常的麻烦,如果这个组件链路上非常的长,就是影响到更多在这条链路上的组件。为了更新方便的解决组件跨层级的传参,一个父组件相对于它所有的后代组件,会作为 依赖提供者(provide
)。任何后代的组件树,无论层级有多深,都能 注入(inject
) 由父组件提供给整条链路的依赖。
2.0 和 3.0 实现原理是有区别的,前者是 while
语句循环,沿着组件树逐级向上查找。后者则是通过原型链向上查找。用法上基本一样
2.0
provide
默认不会使数据成为响应式,inject
依赖的数据默认是响应式的,经过了defineReactive
处理。
// 它会沿着组件树一层一层由内到外的查找,直到根组件
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
}
}
// inject 初始化,在前,可以被 data 选项用到
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);
}
}
// provde 初始化,在后,可以用到 data 里面的数据
// 每个组件实例的 provide 都是相互独立的。
function initProvide (vm) {
var provide = vm.$options.provide;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide;
}
}
3.0
provide
默认不会使数据成为响应式,inject
依赖的数据是根据注入来定的。 为保证注入方和供给方之间的响应性链接,我们需要使用 computed() 函数提供一个计算属性。ref, reactive, 都可
// 当一个组件被创建时, provides 属性初始化的时候,
// 如果父组件存在,则取父组件的 provide,否则,则继承根组件的 provide。
function createComponentInstance(vnode, parent, suspense){
...
const instance = {
...
provides: parent ? parent.provides : Object.create(appContext.provides),
...
}
...
return instance;
}
// 依赖
// 调用 provide 方法时,会先获取当前组件实例的 provide 属性,同时获取父组件的 provide。
// 如果两者相等,说明当前组件实例没有属于自己的 provide,现在的 provide 来源于初始化(如上所示,
// 这时需要通过 Object.create() 给当前组件的 provide 重新设置为继承父组件的对象。
// 然后给通过 key,value 的方式复值。
function provide(key, value) {
if (!currentInstance) {
if ((process.env.NODE_ENV !== 'production')) {
warn(`provide() can only be used inside setup().`);
}
}
else {
let provides = currentInstance.provides;
// by default an instance inherits its parent's provides object
// but when it needs to provide values of its own, it creates its
// own provides object using parent provides object as prototype.
// this way in `inject` we can simply look up injections from direct
// parent and let the prototype chain do the work.
const parentProvides = currentInstance.parent && currentInstance.parent.provides;
if (parentProvides === provides) {
provides = currentInstance.provides = Object.create(parentProvides);
}
// TS doesn't allow symbol as index type
provides[key] = value;
}
}
// 注入
// 当注入的时候,会去先获取当前组件的父组件,如果父组件存,在则说明父组件有依赖 provide,直接返回或者沿着原型链查找,有就返回。如果父组件不存,就直接去根组件中查找,有就返回。如果都没有,就返回默认值,没有默认值就说明这个注入是无效的,开发环境会提示报错,值为 null。
function inject(key, defaultValue, treatDefaultAsFactory = false) {
// fallback to `currentRenderingInstance` so that this can be called in
// a functional component
const instance = currentInstance || currentRenderingInstance;
if (instance) {
// #2400
// to support `app.use` plugins,
// fallback to appContext's `provides` if the instance is at root
const provides = instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides;
if (provides && key in provides) {
// TS doesn't allow symbol as index type
return provides[key];
}
else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance.proxy)
: defaultValue;
}
else if ((process.env.NODE_ENV !== 'production')) {
warn(`injection "${String(key)}" not found.`);
}
}
else if ((process.env.NODE_ENV !== 'production')) {
warn(`inject() can only be used inside setup() or functional components.`);
}
}