Vue源码阅读(32):provide/inject 源码解析

 我的开源库:

provide/inject 的官方文档点击这里

今天和大家讲讲 provide/inject 的内部实现原理,该功能的实现是在 Vue 的实例化阶段,也就是 _init 原型函数中,相关代码如下所示:

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this

    ......

    callHook(vm, 'beforeCreate')
    // 解析初始化当前组件的 inject
    initInjections(vm) // resolve injections before data/props
    // 初始化 state,包括 props、methods、data、computed、watch
    initState(vm)
    // 初始化 provide
    initProvide(vm) // resolve provide after data/props
    // 在 created 回调函数中,可以访问到实例中的数据
    // 执行 created 回调函数
    callHook(vm, 'created') 

    ......
  }
}

执行完 beforeCreate 生命周期函数之后,就开始执行 initInjections(vm) 函数,进行 inject 的初始化。可以发现,inject 的初始化操作比 initState(vm)、initProvide(vm) 都要早,这是为了在当前的组件中 state 和 provide 可以使用当前组件 inject 的值。

inject 和 state 初始化完成之后,进行 provide 的初始化,最后进行 provide 初始化的原因是可以使用 inject 和 state 的值 provide 给下级组件。

1,provide/inject 的工作原理

provide/inject 的工作原理其实很简单,主要分为两部分进行说明,分别是 provide 和 inject。

provide:每个组件 provide 的对象会保存在当前 Vue 实例的 _provided 属性上。

inject:遍历当前组件 inject 数据的 key,然后从当前组件开始,看是否存在 _provided[provideKey],如果存在的话,则将这个值作为 inject key 的值;而如果不存在的话,则以 $parent 为链条查看父组件的 _provided[provideKey] 是否存在,如果还没有的话,则会一级一级的向上级进行查找,inject 数据查找完成之后,借助 defineReactive 将 inject 值设置到当前的 Vue 实例 vm 上。

2,源码解析

2-1,initProvide(vm)

initProvide() 方法很简单,就是将 provide 的对象设置到 Vue 实例的 _provided 属性上。源码如下所示,我写了很多的注释,看注释即可理解。

// provide 选项应该是一个对象或者是一个返回对象的函数
export function initProvide (vm: Component) {
  // 获取存储在 vm.$options 上的 provide 配置
  const provide = vm.$options.provide
  // 如果当前的组件配置了 provide 的话,再进行接下来的逻辑
  if (provide) {
    // provide 选项有可能是对象或者返回对象的函数
    // 如果 provide 是函数的话,则将 provide 函数的返回对象赋值给 vm._provided
    // 如果 provide 就是对象类型的话,则直接将 provide 值赋值给 vm._provided
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

2-2,initInjections(vm)

initInjections() 方法也不难,主要是 inject 值的逐级查找和借助 defineReactive() 方法将查找到的 inject 值定义到当前的组件实例上。源码如下所示,我写了很多注释,看注释即可。

// inject 选项应该是一个字符串数组或者对象
export function initInjections (vm: Component) {
  // resolveInject 方法用于解析 inject 的数据,return 的数据是对象类型
  // 例如:inject: ['foo', 'bar']
  // 生成的 result 对象数据结构如下所示:
  // {
  //   foo: 'xxxxx',
  //   bar: 'cccccc'
  // }
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    // 将 observerState.shouldConvert 设置为 false,这是为了防止 defineReactive 方法
    // 将 result[key] 数据本身进行响应式转换,只将 vm.[key] 转换成响应式就可以了。
    observerState.shouldConvert = false
    // 遍历 result 对象的 keys,对这些 inject 的数据定义到 vm 上,并且 vm.[key] 是响应式的
    Object.keys(result).forEach(key => {
      // 调用 defineReactive 方法将 inject 的数据定义到 vm 上,这样,在组件中就可以
      // 通过 this[injectKey] 访问到 inject 到当前组件中的数据了。
      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])
      }
    })
    // 将 observerState.shouldConvert 恢复设置成 true
    observerState.shouldConvert = true
  }
}

initInjections() 方法内部调用 resolveInject() 方法进行 inject 值逐级查找,源码如下所示,看注释即可。

// 解析 inject 的数据
export function resolveInject (inject: any, vm: Component): ?Object {
  // 代码执行在这里,inject 的数据结构如下所示:
  // {
  //   foo: {
  //     from: 'foo',
  //     default: 'xxxx'
  //   },
  //   bar: {
  //     from: 'jar',
  //     default: function(){
  //       return 'vvvv'
  //     }
  //   }
  // }
  // 为什么代码执行到这里,inject 的数据结构一定是对象的形式呢?inject 也有可能是字符串数组的形式啊?
  // 这是因为在 _init 函数中执行 initInjections 方法之前,会进行 options 的合并操作,这个合并
  // 操作会将 inject 的数据格式统一转换成对象的形式,方便后续代码的处理,所以代码执行到这里,
  // inject 一定是对象的形式
  if (inject) {
    // result 是最终要 return 的对象
    const result = Object.create(null)
    // 获取 inject 的 key 字符串数组
    const keys = hasSymbol
        ? Reflect.ownKeys(inject).filter(key => {
          /* istanbul ignore next */
          return Object.getOwnPropertyDescriptor(inject, key).enumerable
        })
        : Object.keys(inject)
    // 遍历 inject 的 key 字符串数组
    for (let i = 0; i < keys.length; i++) {
      // 当前在遍历的 inject key
      const key = keys[i]
      // 当前遍历的 inject key 对应的注入 provideKey
      const provideKey = inject[key].from
      // source 变量是 Vue 实例的引用,从当前 inject 的 Vue 实例开始,一级一级的向上查找目标 injectKey 的值
      let source = vm
      // 利用 while(){} 一级一级的向上查找
      while (source) {
        // 如果当前的 source 定义了 provide,并且定义的 provide 存在 provideKey 的话,
        // 则说明找到了目标 injectKey 的值
        if (source._provided && provideKey in source._provided) {
          // 将找到的 provideKey 值赋值到 result[key],当前处理的 key 也就完成了
          result[key] = source._provided[provideKey]
          break
        }
        // 如果没有找到的话,则将 source 赋值为当前 Vue 实例的父级 Vue 实例,一级一级的向上找
        // 当找到顶级的 Vue 实例时,他的父级是 null,此时 while 也就结束了。
        source = source.$parent
      }
      // 如果 source 一直处理,到最后被赋值成了 null,则说明 inject key 没有找到目标 key 的 provide 值
      // 此时会进入 inject default 的处理逻辑
      if (!source) {
        // 判断当前的 inject[key] 有没有定义 default
        if ('default' in inject[key]) {
          // 此时,inject[key] 定义了 default,获取 default 定义 provideDefault
          const provideDefault = inject[key].default
          // provideDefault 既有可能是直接的数据,也有可能是返回数据的函数
          // 所以,在这里进行判断 provideDefault 是不是函数类型
          // 如果是函数类型的话,则执行 provideDefault 函数,并将返回值赋值给 result[key]
          // 如果不是函数类型的话,则说明 provideDefault 是直接的数据,将 provideDefault 直接赋值给 result[key] 即可
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          // 如果到最后,default 也没有定义的话,则在非生产环境下,打印出警告
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }
    // 将解析完成的 result 对象 return 出去
    return result
  }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值