资讯专栏INFORMATION COLUMN

人人都能懂的Vue源码系列—05—mergeOptions-上

iKcamp / 2668人阅读

摘要:在解析完其构造函数上的之后,需要把构造函数上的和实例化时传入的进行合并操作并生成一个新的。检查组件名称是否合法首先看传入的三个参数,,这三个参数分别代表的是该实例构造函数上的实例化时传入的实例本身。

前几篇文章中我们讲到了resolveConstructorOptions,它的主要功能是解析当前实例构造函数上的options,不太明白的同学们可以看本系列的前几篇文章。在解析完其构造函数上的options之后,需要把构造函数上的options和实例化时传入的options进行合并操作并生成一个新的options。这个合并操作就是今天要讲的mergeOptions。如果大家不想看枯燥的讲解,可以直接点击人人都能懂的Vue源码系列—04—mergeOptions-下,翻到文章最后,查看整个mergeOptions的流程图。

Merge two option objects into a new one.
Core utility used in both instantiation and inheritance.

先来看源码中对mergeOptions方法的注释。mergeOptions的功能是合并两个options对象,并生成一个新的对象。是实例化和继承中使用的核心方法。可见mergeOptions方法的重要性。既然这么重要,那我就带大家一行一行的来解析代码。保证大家看完这篇文章后,能彻底的理解mergeOptions的作用。

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== "production") {
    checkComponents(child) // 检查组件名称是否合法
  }

  if (typeof child === "function") {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

首先看传入的三个参数,parent,child,vm,这三个参数分别代表的是该实例构造函数上的options,实例化时传入的options,vm实例本身。结合Vue作者写的注释,我们明白了,原来mergeoptions方法是要合并构造函数和传入的options这两个对象。
明白了这点之后,接下来往下看

if (process.env.NODE_ENV !== "production") {
    checkComponents(child) // 检查组件名称是否合法
 }

这段代码主要是判断当前环境是不是生产环境,如果不是,则调用checkComponents方法来检查组件名称是否是可用名称。我们来看看checkComponents的逻辑。

function checkComponents (options: Object) {
  for (const key in options.components) {
    validateComponentName(key)
  }
}
export function validateComponentName (name: string) {
  if (!/^[a-zA-Z][w-]*$/.test(name)) {
    warn(
      "Invalid component name: "" + name + "". Component names " +
      "can only contain alphanumeric characters and the hyphen, " +
      "and must start with a letter."
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      "Do not use built-in or reserved HTML elements as component " +
      "id: " + name
    )
  }
}

如果child的options(实例化传入的options)有components属性。如下面这种情况

const app = new Vue({
   el: "#app",
   ...
   components: {
     childComponent
   }
   ...
})

那么就调用validateComponentName来验证传入的组件名称是否符合以下特征。

包含数字,字母,下划线,连接符,并且以字母开头

是否和html标签名称或svg标签名称相同

是否和关键字名称相同,如undefined, infinity等

如果满足第一条,并且第2,3条都是不相同的话,那么组件名称可用。
我们再回到mergeOptions源码中

if (typeof child === "function") {
    child = child.options
}

如果child是function类型的话,我们取其options属性作为child。
接下来看这三个方法以normalize开头的方法

normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)

这三个方法的功能类似,分别是把options中的props,inject,directives属性转换成对象的形式。因为有些传入的时候可能会是数组的形式。如

Vue.component("blog-post", {
  props: ["postTitle"],
  template: "

{{ postTitle }}

" })
normalizeProps

我们先来看props处理的逻辑

function normalizeProps (options: Object, vm: ?Component) {
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === "string") {
        name = camelize(val)
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== "production") {
        warn("props must be strings when using array syntax.")
      }
    }
  } else if (isPlainObject(props)) {
    for (const key in props) {
      val = props[key]
      name = camelize(key)
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== "production") {
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

首先明确这两个方法里的参数是什么,options传入的是child,即实例化时传入的options。vm是实例。知道了这两个参数是什么,我们继续来研究代码。

const props = options.props
if (!props) return
const res = {}
let i, val, name

上面的代码主要是声明一些变量。res用来存放修改后的props,最后把res赋给新的props。下面的逻辑可以分为两种情况来考虑

props是数组

当props是数组的时候,如下面这种情况

Vue.component("blog-post", {
  props: ["postTitle"],
  template: "

{{ postTitle }}

" })

它的处理逻辑是,遍历props数组,把数组的每一项的值作为res对象的key,value值等于{type: null},即把上面例子中的["postTitle"]转换成下面这种形式

{
  postTitle: { type: null }
}
props是对象

当props是对象时,如下面这种情况

Vue.component("my-component", {
  props: {
    // 必填的字符串
    propC: {
      type: String,
      required: true
    }
  }
})

这种情况的处理逻辑是遍历对象,先把对象的key值转换成驼峰的形式。然后再判断对象的值,如果是纯对象(即调用object.prototype.toString方法的结果是[object Object]),则直接把对象的值赋值给res,如果不是,则把{ type: 对象的值}赋给res。最终上面这种形式会转换成

{
  propC: {
   type: String,
   required: true
  }
}

如果传入的props不是纯对象也不是数组,且当前环境也不是生产环境,则抛出警告。

warn(
  `Invalid value for option "props": expected an Array or an Object, ` +
  `but got ${toRawType(props)}.`,
   vm
)

最后,把处理过的props重新赋值给options.props。

normalizeInject

这个方法的逻辑和normalizeProps类似,主要是处理inject。inject属性如果大家平时不是写库或者插件的话,可能很少接触到,可以先查看inject的使用,inject的传入和props类似。可以传入object,也可以传入array

// array
var Child = {
  inject: ["foo"],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}
// object
const Child = {
  inject: {
    foo: {
      from: "bar",
      default: "foo"
    }
  }
}

由于这个方法和normalizeProps逻辑基本一样,这里也不具体展开讲了。上面的demo最终会被转换成如下形式

// array
{
  foo: { from: "foo"}
}
// object
{
 foo: {
   from: "bar",
   default: "foo"
 }
}
normalizeDirectives

这个方法主要是处理一些自定义指令,如果不了解自定义指令的同学可以自定义指令。这里的方法处理逻辑主要针对自定义指令中函数简写的情况。如下

Vue.directive("color", function (el, binding) {
  el.style.backgroundColor = binding.value
})

normalizeDirectives构造函数会把这个指令传入的参数,最终转换成下面这种形式

  color: {
    bind: function (el, binding) {
      el.style.backgroundColor = binding.value
    },
    update: function (el, binding) {
      el.style.backgroundColor = binding.value
    }
  }

由于文章篇幅所限,本篇文章先讲解到这里,下篇继续带大家来看mergeOptions的实现。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107757.html

相关文章

  • 人人都能懂的Vue源码系列—04—resolveConstructorOptions函数-下

    摘要:上一篇文章中说道,函数要分两种情况进行说明,第一种是为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况,是创建的子类。表示的是当前构造器上新增的,表示的是当前构造器上新增的封装。 上一篇文章中说道,resolveConstructorOptions函数要分两种情况进行说明,第一种是Ctor为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况...

    My_Oh_My 评论0 收藏0
  • 人人都能懂的Vue源码系列—04—resolveConstructorOptions函数-下

    摘要:上一篇文章中说道,函数要分两种情况进行说明,第一种是为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况,是创建的子类。表示的是当前构造器上新增的,表示的是当前构造器上新增的封装。 上一篇文章中说道,resolveConstructorOptions函数要分两种情况进行说明,第一种是Ctor为基础构造器的情况,这个已经向大家介绍过了,今天这篇文章主要介绍第二种情况...

    BlackHole1 评论0 收藏0
  • 人人都能懂的Vue源码系列—06—mergeOptions-下

    摘要:下面分别为大家介绍钩子函数的策略所有关于钩子函数的策略,其实都是调用方法。的策略介绍完了钩子函数的合并策略,我们接下来看等属性的合并策略。如果当前实例或者构造函数上有一个没有属性,则返回另一个的属性,如果两者都有,则同样调用方法处理合并。 上篇文章,我们讲到了mergeOptions的部分实现,今天接着前面的部分讲解,来看代码,如果大家觉得看讲解枯燥可以直接翻到本文的最后看mergeO...

    megatron 评论0 收藏0
  • 人人都能懂的Vue源码系列—03—resolveConstructorOptions函数-

    摘要:上篇文章介绍了构造函数的部分实现,当前实例不是组件时,会执行方法。这个文件就是对构造函数进行的第一层包装了。但是注意这里的代码我们构造函数的第二层包装,就在这个文件里了。回到的源码中,当不存在时,直接返回基础构造器的。 上篇文章介绍了Vue构造函数的部分实现,当前Vue实例不是组件时,会执行mergeOptions方法。 vm.$options = mergeOptions( re...

    snifes 评论0 收藏0
  • 人人都能懂的Vue源码系列—02—Vue构造函数

    摘要:果然我们找到了的构造函数定义。告诉你是一个构造函数,需要用操作符去调用。在深入方法之前,我们先把目光移到文件里在的构造函数定义之后,有一系列方法会被立即调用。下篇博文主要介绍相关的内容,涉及到原型链和构造函数以及部分的实现,敬请期待 上篇博文中说到了Vue源码的目录结构是什么样的,每个目录的作用我们应该也有所了解。我们知道core/instance目录主要是用来实例化Vue对象,所以我...

    X_AirDu 评论0 收藏0

发表评论

0条评论

iKcamp

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<