从vue 源码中看看 provide 和 inject

·  阅读 725

要搞清楚为什么,首先得知道是什么

基本的使用就是从父组件拿到值,然后子组件用,一下是从官网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 的数据变成响应式的根本原因。

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改