要搞清楚为什么,首先得知道是什么
基本的使用就是从父组件拿到值,然后子组件用,一下是从官网copy下来的基本用法,其他语法参见官网 cn.vuejs.org/v2/api/#pro…
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
首先打开源码 /core/instance/init.js,文件,vue的初始化过程,这里是一次执行了这三个方法
- initInjections
- initProvide
- callHook(vm, 'created') 所以,这个就解释了为什么能够在 created 中拿到 inject 接收到的数据
initInjections 方法到底干了什么
方法被定义在源码的 /core/instance/inject.js 文件中
export function initInjections (vm: Component) {
// 拿到组件中的 inject 配置项,经过处理,放在了result 中
const result = resolveInject(vm.$options.inject, vm)
if (result) {
// 不把这些属性设置为响应式,这就是官网说的 provide/inject 绑定没有响应式的原因
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 方法,只是把 key 代理到实例上,
// 并没有给每个属性设置响应式,因为响应式的核心是 observe 方法,
// 而在实例的时候需要通过 toggleObserveing 设置 shouldObserve 变量
// shouldObserve 变量作为判断是否需要实例化 Observe。这里并没有
defineReactive(vm, key, result[key])
}
})
toggleObserving(true)
}
}
- 首先执行了 resoleveInject 方法, 那就找到该方法,在同级目录下面
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
// 直接从inject 属性中拿到 from 的值,是因为作了标准化处理,都把每个对象都设置成了带 from 的形式
const provideKey = inject[key].from
let source = vm
// 直接从当前的实例上的 provide 开始取值
// 不断地向上查找祖先组件,拿到其 provide 的值,放到 result 中
// 这也就解释了,为什么,先初始化 inject 而不是 provide
// 因为:遍历 provide 是直接从当前组件开始的,如果先 初始化 provide
// 那么 result 中也会包含当前组件中的 provide 的值,而 provide/inject 解决的是当前组件接受到父组件的值
while (source) {
if (source._provided && hasOwn(source._provided, provideKey)) {
result[key] = source._provided[provideKey]
break
}
source = source.$parent
}
// 如果查找整个祖先元素,没有发现 provide 选项,那么会查找是否 inject 中的默认值来使用
// 如果没有,就会报错了
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 干了什么
进去发现 provide 没什么事儿,就是根据 provide 选项的两种形式,如果是函数返回函数的执行结果,如果是对象,直接返回对象到当前实例上面。
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
总结一下
-
整个 provide/inject 机制是什么
- 是当前组件通过遍历所有的祖先组件,拿到 provide 选项,从而拿到所有的值,供当前组件使用
-
为什么先 初始化 inject 而不是 provide
- 因为遍历拿到 provide 选项是从当前组件开始遍历的,如果先初始化 provide ,也会拿到当前 组件的 provide 放到inject 中,显然不是
-
inject 中的数据不是响应式的
-
关键的方法
toggleObserving(false),执行操作把变量 shouldObserve 设置为 false ,这个事关键性的响应式的实例 Observer 是否实例化的关键,所以后面执行了 defineReactive 并没有把inject 的数据变成响应式的根本原因。
-