【Vue2.0源码学习】生命周期篇-初始化阶段(initInjections)

这篇具有很好参考价值的文章主要介绍了【Vue2.0源码学习】生命周期篇-初始化阶段(initInjections)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前言

本篇文章介绍生命周期初始化阶段所调用的第四个初始化函数——initInjections。从函数名字上来看,该函数是用来初始化实例中的inject选项的。说到inject选项,那必然离不开provide选项,这两个选项都是成对出现的,它们的作用是:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。并且

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

inject 选项应该是:

  • 一个字符串数组,或
  • 一个对象,对象的 key 是本地的绑定名,value 是:
    • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
    • 一个对象,该对象的:
      • from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
      • default 属性是降级情况下使用的 value

这两个选项在我们日常开发中使用的频率不是很高,但是在一些组件库中使用的很频繁,官方文档给出了使用示例,如下:

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

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

利用 ES2015 Symbols、函数 provide 和对象 inject

const s = Symbol()

const Provider = {
  provide () {
    return {
      [s]: 'foo'
    }
  }
}

const Child = {
  inject: { s },
  // ...
}

接下来 2 个例子只工作在 Vue 2.2.1 或更高版本。低于这个版本时,注入的值会在 propsdata 初始化之后得到。

使用一个注入的值作为一个属性的默认值:

const Child = {
  inject: ['foo'],
  props: {
    bar: {
      default () {
        return this.foo
      }
    }
  }
}

使用一个注入的值作为数据入口:

const Child = {
  inject: ['foo'],
  data () {
    return {
      bar: this.foo
    }
  }
}

在 2.5.0+ 的注入可以通过设置默认值使其变成可选项:

const Child = {
  inject: {
    foo: { default: 'foo' }
  }
}

如果它需要从一个不同名字的属性注入,则使用 from 来表示其源属性:

const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: 'foo'
    }
  }
}

与 prop 的默认值类似,你需要对非原始值使用一个工厂方法:

const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3]
    }
  }
}

总结起来一句话就是:父组件可以使用provide选项给自己的下游子孙组件内注入一些数据,在下游子孙组件中可以使用inject选项来接收这些数据以便为自己所用。

另外,这里有一点需要注意:provideinject 选项绑定的数据不是响应式的。

了解了他们的作用及使用方法后,我们就来看下initInjections函数是如何来初始化inject选项的。

2. initInjections函数分析

分析之前,我们先说一个问题,细心的同学可能会发现,既然inject选项和provide选项都是成对出现的,那为什么在初始化的时候不一起初始化呢?为什么在init函数中调用initInjections函数和initProvide函数之间穿插一个initState函数呢?

其实不然,在官方文档示例中说了,provide选项注入的值作为数据入口,如下:

const Child = {
  inject: ['foo'],
  data () {
    return {
      bar: this.foo
    }
  }
}

这里所说的数据就是我们通常所写datapropswatchcomputedmethod,所以inject选项接收到注入的值有可能被以上这些数据所使用到,所以在初始化完inject后需要先初始化这些数据,然后才能再初始化provide,所以在调用initInjections函数对inject初始化完之后需要先调用initState函数对数据进行初始化,最后再调用initProvide函数对provide进行初始化。

OK,接下来我们就来分析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
}

可以看到,initInjections函数的逻辑并不复杂,首先调用resolveInjectinject选项中的数据转化成键值对的形式赋给result,如官方文档给出的例子,那么result应为如下样子:

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

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

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

然后遍历result中的每一对键值,调用defineReactive函数将其添加当前实例上,如下:

if (result) {
    toggleObserving(false)
    Object.keys(result).forEach(key => {
        defineReactive(vm, key, result[key])
    }
    toggleObserving(true)
}

此处有一个地方需要注意,在把result中的键值添加到当前实例上之前,会先调用toggleObserving(false),而这个函数内部是把shouldObserve = false,这是为了告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式,这个就呼应了官方文档在介绍provideinject 选项用法的时候所提示的:

provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

initInjections函数的逻辑就介绍完了,接下来我们看看resolveInject函数内部是如何把inject 选项中数据转换成键值对的。

resolveInject函数分析

我们知道,inject 选项中的每一个数据key都是由其上游父级组件提供的,所以我们应该把每一个数据key从当前组件起,不断的向上游父级组件中查找该数据key对应的值,直到找到为止。如果在上游所有父级组件中没找到,那么就看在inject 选项是否为该数据key设置了默认值,如果设置了就使用默认值,如果没有设置,则抛出异常。

OK,以上是我们的分析,下面我们就来看下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
  }
}

在分析函数源码之前,我们对照着官网给出的示例,这样会比较好理解一些。

var Parent = {
  provide: {
    foo: 'bar'
  },
  // ...
}
const Child = {
  inject: {
    foo: {
      from: 'bar',
      default: () => [1, 2, 3]
    }
  }
}

可以看到,在函数源码中,首先创建一个空对象result,用来存储inject 选项中的数据key及其对应的值,作为最后的返回结果。

然后获取当前inject 选项中的所有key,然后遍历每一个key,拿到每一个keyfrom属性记作provideKeyprovideKey就是上游父级组件提供的源属性,然后开启一个while循环,从当前组件起,不断的向上游父级组件的_provided属性中(父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中)查找,直到查找到源属性的对应的值,将其存入result中,如下:

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
  }
}

如果没有找到,那么就看inject 选项中当前的数据key是否设置了默认值,即是否有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)
  }
}

最后将result返回。这就是resolveInject函数的所有逻辑。

此时你可能会有个疑问,官方文档中说inject 选项可以是一个字符串数组,也可以是一个对象,在上面的代码中只看见了处理当为对象的情况,那如果是字符串数组呢?怎么没有处理呢?

其实在初始化阶段_init函数在合并属性的时候还调用了一个将inject 选项数据规范化的函数normalizeInject,该函数的作用是将以下这三种写法:

// 写法一
var Child = {
  inject: ['foo']
}

// 写法二
const Child = {
  inject: {
    foo: { default: 'xxx' }
  }
}

// 写法三
const Child = {
  inject: {
    foo
  }
}

统统转换成以下规范化格式:

const Child = {
  inject: {
    foo: {
      from: 'foo',
      default: 'xxx'  //如果有默认的值就有default属性
    }
  }
}

这样做的目的是,不管用户使用了何种写法,统统将其转化成一种便于集中处理的写法。

该函数的定义位于源码的src/core/util/options.js中,如下:

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选项传入的是一个字符串数组(写法一),那么就遍历该数组,把数组的每一项变成

inject:{
  foo:{
    from:'foo'
  }
}

如果给inject选项传入的是一个对象,那就遍历对象中的每一个key,给写法二形式的key对应的值扩展{ from: key },变成:

inject:{
  foo:{
    from: 'foo',
    default: 'xxx'
  }
}

将写法三形式的key对应的值变成:

inject:{
  foo:{
    from: 'foo'
  }
}

总之一句话就是把各种写法转换成一种规范化写法,便于集中处理。

3. 总结

本篇文章介绍生命周期初始化阶段所调用的第四个初始化函数——initInjections。该函数是用来初始化inject选项的。

由于inject选项在日常开发中使用频率不高,所以首先我们先根据官方文档回顾了该选项的作用及使用方法。

接着,我们分析了initInjections函数的内部实现原理,分析了是根据inject选项中的数据key是如何自底向上查找上游父级组件所注入的对应的值。

另外,对inject选项的规范化函数normalizeInject也进行了分析,Vue为用户提供了自由多种的写法,其内部是将各种写法最后进行统一规范化处理。文章来源地址https://www.toymoban.com/news/detail-696580.html

到了这里,关于【Vue2.0源码学习】生命周期篇-初始化阶段(initInjections)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue2的生命周期详解(代码演示+源码)

            生命周期是指从开始创建、初始化数据、编译模版、挂载 Dom - 渲染、更新 - 渲染、卸载等一系列过程,我们称这是 Vue 的生命周期,它主要强调一个时间段。用一句话来概括就是: Vue实例的生命周期: 从创建到销毁的整个过程 Vue框架内置函数,随着组件的生命周期阶

    2024年02月04日
    浏览(48)
  • 【Spring Boot 源码学习】ConditionEvaluationReport 日志记录上下文初始化器

    《Spring Boot 源码学习系列》 上篇博文《共享 MetadataReaderFactory 上下文初始化器》, Huazie 带大家详细分析了 SharedMetadataReaderFactoryContextInitializer 。而在 spring-boot-autoconfigure 子模块中预置的上下文初始化器中,除了共享 MetadataReaderFactory 上下文初始化器,还有一个尚未分析。 那么

    2024年04月13日
    浏览(41)
  • 【SA8295P 源码分析】86 - AIS Camera Device 设备初始化 之 AisProcChainManager 模块初始化源码分析

    【源码分析】 因为一些原因,本文需要移除, 对于已经购买的兄弟,不用担心,不是跑路, 我会继续持续提供技术支持, 有什么模块想学习的,或者有什么问题有疑问的, 请私聊我,我们 +VX 沟通技术问题,一起学习,一起进步 接下来,我一一私聊已经购买的兄弟添加V

    2024年02月10日
    浏览(44)
  • canal server初始化源码分析

    在开始之前,我们可以先了解下, canal 配置方式 ManagerCanalInstanceGenerator: 基于manager管理的配置方式,实时感知配置并进行server重启 SpringCanalInstanceGenerator:基于本地spring xml的配置方式,对于多instance的时候,不便于扩展,一个instance一个xml配置 canal 配置文件 canal.properties  

    2024年01月19日
    浏览(40)
  • SpringMVC源码解析——DispatcherServlet初始化

    在Spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型的实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现Servlet接口的实现类。Servlet是一个JAVA编写的程序,此程序是基于HTTP协议的,在服务端运行的(如Tomcat),是按照Servlet规范

    2024年02月03日
    浏览(43)
  • vue中初始化

    主要是挂载一些全局方法 响应数据相关的Vue.set, Vue.delete, Vue.nextTick以及Vue.observable 插件相关的Vue.use 对象合并相关Vue.mixin 类继承相关的Vue.extend 资源相关,如组件,过滤器,自定义指令Vue.component, Vue.filter, Vue.directive 配置相关Vue.config以及Vue.options中的components,filters,directives 定

    2023年04月22日
    浏览(47)
  • 深度学习参数初始化(二)Kaiming初始化 含代码

    目录 一、介绍 二、基础知识 三、Kaiming初始化的假设条件  四、Kaiming初始化的简单的公式推导 1.前向传播 2.反向传播 五、Pytorch实现 深度学习参数初始化系列: (一)Xavier初始化 含代码 (二)Kaiming初始化 含代码         Kaiming初始化论文地址:https://arxiv.org/abs/1502.01

    2024年02月04日
    浏览(75)
  • UE4 源码解析----引擎初始化流程

       在研究UE4的源码过程中着实不理解的地方有很多,今天给大家分享一下UE4引擎的初始化流程。 C++的函数入口都是Main() 函数入口,UE4也是一样,EngineSourceRuntimeLaunchPrivate Windows函数入口  引擎入口函数为:GuardedMain UE4所有相关的代码都在游戏循环函数中,在Launch.cpp中,写

    2024年02月06日
    浏览(85)
  • Vue初始化项目加载逻辑

    项目创建 我们只需要创建项目即可,剩余的依赖都没必要安装 我们先来看main.js,咱们加了一行备注 通过备注可知,我们首先加载的是App.vue 我们再来看一下App.vue 里都有啥 也就是下面这个红框里的内容才是 那下面的内容是哪里来的呢 那就需要看一下路由设置了 我们看到/目

    2024年02月08日
    浏览(103)
  • 【源码解析】聊聊SpringBean是如何初始化和创建

    我们知道通过类进行修复不同的属性,比如单例、原型等,而具体的流程是怎么样的呢,这一篇我们开始从源码的视角分析以下。 在刷新容器中有一个方法,其实就是 Bean创建的过程。 而BeanFactory中 preInstantiateSingletons是初始化所有的bean对象的核心流程。 而这里通过去遍历所

    2024年02月05日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包