通过Vue源码 查看 provide / inject 的实现

295 阅读4分钟

众所周知 Vue 跨层级组件之间的通讯方式之一就是provide/inject

我们今天就通过Vue 的源码 来看看 provide / inject 是如何实现的;

我们先看一下 provide / inject 官方的解释:

provide / inject

2.2.0 新增

  • 类型

    • provideObject | () => Object
    • injectArray<string> | { [key: string]: string | Symbol | Object }
  • 详细

    这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

    provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。

    inject 选项应该是:

    • 一个字符串数组,或

    • 一个对象,对象的 key 是本地的绑定名,value 是:

      • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或

      • 一个对象,该对象的:

        • from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)

        • default property 是降级情况下使用的 value

    提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

我们通过文档对 provide / inject 有了一个基本认识,我们简单总结一下:

  1. provide / inject 需要一起使用,简单说就是一对,不可单独使用,如果单独使用不会报错,但是无效;
  2. 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。也就是说 provide工作在上层,inject 工作在下层;只要他们的层节点是上下级关系,不能嵌套多深都可以;
  3. provide 的类型是object 或者 一个反对对象的函数
  4. 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文档,理解源码其实并不难

最后的最后

下面请上我们今天的主角:有请小趴菜

小趴菜.jpeg