欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Vue源码解析:Vue生命周期之从生到死(三)

程序员文章站 2023-03-08 17:21:01
上俩片文章我们讲到了initLifecycle和initEvents函数,本篇是时候讲讲initInjections初始化函数了。...

上俩片文章我们讲到了initLifecycle和initEvents函数,本篇是时候讲讲initInjections初始化函数了。该函数就是用来去实例化inject选项。说到inject,那就必然离不开provide,这俩是成对出现的,它们的作用是:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

provide:是一个对象,或者是一个返回对象的函数。

inject:是一个字符串数组,或者是一个多层对象。

这俩个东西,在组件中用的比较多。总结来说,就是父组件可以使用provide选项给自己的下游子孙组件内注入一些数据,在下游子孙组件中可以使用inject选项来接收这些数据以便为自己所用。provide 和 inject 选项绑定的数据不是响应式的。

initInjections是何方神圣?

我们发现,provide和inject函数是成对出现,但是,在init的初始化中发现,provide和inject之间有一个initstate函数,其实不然,provide选项注入的值作为数据入口,也就是datapropswatchcomputedmethod,所以inject之前可能会被以上的这些使用到,所以在初始化inject后需要初始化这些数据,然后才能初始化provide。在调用initInjections函数对inject初始化完之后需要先调用initState函数对数据进行初始化,最后再调用initProvide函数对provide进行初始化。

initInjections函数的原理:

//    src/core/instance/inject.js
export function initInjections (vm: Component) {
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
      defineReactive(vm, key, result[key])
    }
    toggleObserving(true)
  }
}

export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {
  shouldObserve = value
}

首先调用resolveInjectinject选项中的数据转化成键值对的形式赋给result,result的结果应该是这样的。

// 父级组件提供 'foo'
var Parent = {
  provide: {
    foo: 'bar'
  }
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
}

// result
result = {
    'foo':'bar'
}

然后遍历result中的每一对键值,调用defineReactive函数将其添加当前实例上。我们发现遍历之前调用了toggleObserving(false)函数,这是在告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式。要不然如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

resolveInject又干了什么?

export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    const result = Object.create(null)
    const keys =  Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      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
  }
}

首先创建一个空对象result,用来存储inject 选项中的数据key及其对应的值,作为最后的返回结果。然后获取当前inject 选项中的所有key,然后遍历每一个key,拿到每一个keyfrom属性记作provideKeyprovideKey就是上游父级组件提供的源属性,然后开启一个while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中。如果没有找到,那么就看inject 选项中当前的数据key是否设置了默认值,即是否有default属性,如果有的话,则拿到这个默认值,官方文档示例中说了,默认值可以为一个工厂函数,所以当默认值是函数的时候,就去该函数的返回值,否则就取默认值本身。如果没有设置默认值,则抛出异常。最后返回result。

ok,之前说到inject是一个字符串数组,但是没有处理,这是为什么呢?其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject。这样做的目的是,不管用户使用了何种写法,统统将其转化成一种便于集中处理的写法。

function normalizeInject (options: Object, vm: ?Component) {
  const inject = options.inject
  if (!inject) return
  const normalized = options.inject = {}
  if (Array.isArray(inject)) {
    for (let i = 0; i < inject.length; i++) {
      normalized[inject[i]] = { from: inject[i] }
    }
  } else if (isPlainObject(inject)) {
    for (const key in inject) {
      const val = inject[key]
      normalized[key] = isPlainObject(val)
        ? extend({ from: key }, val)
        : { from: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    warn(
      `Invalid value for option "inject": expected an Array or an Object, ` +
      `but got ${toRawType(inject)}.`,
      vm
    )
  }
}

 这个函数的逻辑不复杂,也是一目了然,就是在处理传入的inject,将其格式化。。。。

未完,待续。。。

本文地址:https://blog.csdn.net/leelxp/article/details/107131649