众所周知 Vue 跨层级组件之间的通讯方式之一就是provide/inject
我们今天就通过Vue 的源码 来看看 provide / inject 是如何实现的;
我们先看一下 provide / inject 官方的解释:
provide / inject
2.2.0 新增
-
类型:
- provide:
Object | () => Object - inject:
Array<string> | { [key: string]: string | Symbol | Object }
- provide:
-
详细:
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持Symbol和Reflect.ownKeys的环境下可工作。inject选项应该是:-
一个字符串数组,或
-
一个对象,对象的 key 是本地的绑定名,value 是:
-
在可用的注入内容中搜索用的 key (字符串或 Symbol),或
-
一个对象,该对象的:
-
fromproperty 是在可用的注入内容中搜索用的 key (字符串或 Symbol) -
defaultproperty 是降级情况下使用的 value
-
-
提示:
provide和inject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。 -
我们通过文档对 provide / inject 有了一个基本认识,我们简单总结一下:
- provide / inject 需要一起使用,简单说就是一对,不可单独使用,如果单独使用不会报错,但是无效;
- 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。也就是说 provide工作在上层,inject 工作在下层;只要他们的层节点是上下级关系,不能嵌套多深都可以;
- provide 的类型是object 或者 一个反对对象的函数
provide和inject绑定并不是可响应的。如果需要 你可以自己传入一个相应示对象;
那么 我们看下源码他是怎么跨层级通讯的; 我们先贴出源码:gitee.com/vuejs/vue/b…
/* @flow */
import { hasOwn } from 'shared/util'
import { warn, hasSymbol } from '../util/index'
import { defineReactive, toggleObserving } from '../observer/index'
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let 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]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result
}
}
我们通过源码文件看到了 主要的函数有: initProvide initInjections resolveInject
我们通过添加注释的方式来解析一个一个
initProvide:
export function initProvide (vm: Component) {
// 从vue options 对象上拿到 provide 选项
const provide = vm.$options.provide
// 如果 provide 配置的有内容 就给当前 实例 vm 扩展一个 私有属性 _provided 内容就是 provide 的内容结果,这里判断是不是function了,并且修改了this 指向了当前的实例;
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
这个初始化 provide 的操作非常简单, 下面我们看 初始化inject 函数:
export function initInjections (vm: Component) {
// 通过 resolveInject 函数 拿到 当前实例的的inject 结果;
const result = resolveInject(vm.$options.inject, vm)
// 如果不为空
if (result) {
// 标记 Observing 为 false
toggleObserving(false)
// 这里循环遍历 result 给添加响应式。
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, key, result[key], () => {
warn(
`Avoid mutating an injected value directly since the changes will be ` +
`overwritten whenever the provided component re-renders. ` +
`injection being mutated: "${key}"`,
vm
)
})
} else {
defineReactive(vm, key, result[key])
}
})
// 在标记Observing为true
toggleObserving(true)
// 完成响应式的添加;
}
}
最后我们来看一下 resolveInject 的实现
export function resolveInject (inject: any, vm: Component): ?Object {
// 判断是否有 inject
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
// 创建了一个 空对象;
const result = Object.create(null)
拿到了 所有 inject的key,
const keys = hasSymbol
? Reflect.ownKeys(inject)
: Object.keys(inject)
对这些key 进行了遍历,
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
// 这里跳过了 __ob__,因为它上面存放的是对象的响应式
if (key === '__ob__') continue
// 找到 provideKey
const provideKey = inject[key].from
let source = vm
// 然后这个地方就开始了 while 循环 开始向上查找 对于的provide
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
// 吧 找到的 provide 给 result 对应的key
result[key] = source._provided[provideKey]
break
}
source = source.$parent // 不断的向上查找,
}
// 这里 处理了 没有 source 使用 default 的场景,
if (!source) {
if ('default' in inject[key]) {
const provideDefault = inject[key].default
result[key] = typeof provideDefault === 'function'
? provideDefault.call(vm)
: provideDefault
} else if (process.env.NODE_ENV !== 'production') {
warn(`Injection "${key}" not found`, vm)
}
}
}
return result // 最后返回result
}
}
这样就 provide / inject 的整个过程就分析完了,是不是很简单。
其实 我们 认真看 结合相应的API文档,理解源码其实并不难
最后的最后
下面请上我们今天的主角:有请小趴菜