Yan Dong, Front End Engineer, Front End Technology Department, Wemicro. Aspiring to become a full stack development engineer or even architect, I have a long way to go. Exercise in your life to release stress and think about problems.

The article is long, so it is recommended to spend a whole block reading the analysis. In addition, because the length is too long, this paper is divided into three articles output, easy for everyone to understand and read.

MergeOptions function

In an article on the analysis, we have finished analysis resolveConstructorOptions mergeOptions function of the first parameter, we look back at the function mergeOptions again.

Merges the options passed in the constructor with the options passed in the instantiation and generates a new option

The mergeOptions function merges the options on the instance constructor, the options passed in during the instantiation, and the current instance into a single option.

export function mergeOptions (

  parent: Object.// Options on the instance constructor

  child: Object.// Options passed in when instantiatingvm? : Component// The current instance

) :Object {

  if(process.env.NODE_ENV ! = ='production') {

    checkComponents(child)

  }

  if (typeof child === 'function') {

    child = child.options

  }

  normalizeProps(child, vm)

  normalizeInject(child, vm)

  normalizeDirectives(child)

  // Apply extends and mixins on the child options,

  // but only if it is a raw options object that isn't

  // the result of another mergeOptions call.

  // Only merged options has the _base property.

  if(! child._base) {if (child.extends) {

      parent = mergeOptions(parent, child.extends, 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

}

Copy the code
  • 1.checkComponents(child)Verify that the component name is valid
  • 2. If child (options) is of type function, we take its options property as child
  • 3. Transform the props, inject, and directives into object form (normalizeProps(child, vm).normalizeInject(child, vm).normalizeDirectives(child))

Article 1 and article 2 we will not do a detailed analysis, interested can consult. Component names, we should not be too special when using them

normalizeProps

Let’s see how Vue converts props. First, we need to understand the use of props. Generally, we have two types of props, one is an array form, and the other is an object form.

// It is an array

Vue.component('test-component', {

  props: ['testData'].template: '<span>{{ testData }}</span>'

})

// Object form

Vue.component('test-component', {

  props: {

    testData: {

      type: String.default: ' '}},template: '<span>{{ testData }}</span>'

})

Copy the code

Look again at the definition of the normalizeProps function

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

}

Copy the code

This code declares the res variable, which is used to store the processed object, and assigns it to options.props.

  • If props is an array, traverse an array of strings, assigning each key to {type: null}

That is, arrays are converted to:

{

    testData: { type: null}}Copy the code
  • If the props is an object, then the props is humped, and the value corresponding to the key is a pure object. If the value corresponding to the key is a pure object, the props is humped, and the res is returned. Instead of assigning {type: value} to res, the props of the object form should be converted to:
{

    testData: {

      type: String.default: ' '}}Copy the code

normalizeInject

/** * Normalize all injections into Object-based format */

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

    )

  }

}

Copy the code

Also score groups and objects:

// Parent component provides 'foo'

var Provider = {

  provide: {

    foo: 'bar'

  },

  // ...

}

// Inject 'foo' as an array of subcomponents

var Child = {

  inject: ['foo'],

  created () {

    console.log(this.foo) // => "bar"

  }

  // ...

}

// Inject 'foo' as a child object

const Child = {

  inject: {

    foo: {

      from: 'bar'.default: 'foo'}}}Copy the code

NormalizeInject will eventually become:

// array

{

  foo: { from: 'foo'}}// object

{

foo: {

  from: 'bar'.default: 'foo'}}Copy the code

normalizeDirectives

/** * Normalize raw function directives into object format. */

function normalizeDirectives (options: Object) {

  const dirs = options.directives

  if (dirs) {

    for (const key in dirs) {

      const def = dirs[key]

      if (typeof def === 'function') {

        dirs[key] = { bind: def, update: def }

      }

    }

  }

}

Copy the code

Directives function and object are written:

// Object registration

Vue.directive('my-directive', {

  bind: function () {},

  inserted: function () {},

  update: function () {},

  componentUpdated: function () {},

  unbind: function () {}})// Function registration (instruction function)

Vue.directive('my-directive'.function () {

  // This is called by 'bind' and 'update'

})

Copy the code

The source code will treat functional instructions as bind and update calls, which means that functional instructions will be treated as:

{

  'my-directive': {

    bind: function () {},

    update: function () {}}}Copy the code

Let’s look at the code below mergeOptions:

    if (child.extends) {

      parent = mergeOptions(parent, child.extends, vm)

    }

    if (child.mixins) {

      for (let i = 0, l = child.mixins.length; i < l; i++) {

        parent = mergeOptions(parent, child.mixins[i], vm)

      }

    }

Copy the code

When a mixin or extends property is passed to an options (Child), Call mergeOptions again to merge mixins and extends into the instance constructor options (parent Options).

Lift chestnuts 🌰 :

const childComponent = Vue.component('child', {...mixins: [myMixin],

      extends: myComponent

      ...

})

const myMixin = {

      created: function () {

        this.hello()

      },

      methods: {

        hello: function () {

          console.log('hello from mixin')}}}const myComponent = {

      mounted: function () {

        this.goodbye()

      },

      methods: {

        goodbye: function () {

          console.log('goodbye from mixin')}}}Copy the code

Mounted, created hook handlers, and methods are merged with parent options.

That’s the end of this piece. The mergeOptions function tells the following:

  • Props is normalized to object form{type: null} or {type: val}
  • Inject is normalized as an object{from: val}
  • Standardization of directivesThe bind, the update function
  • The extends, mixins property of the passed option is merged

summary

In the next article, we’ll dive into the core code of the mergeOptions function: merge strategy.

Finally finished the analysis of merge options (three articles), every day out of sleep time to chew this, it is not easy to promote their growth, I hope to bring some inspiration to the cute, if there is any place feel not clear welcome to ask questions in the comments section below, discussion ~

See the little lovely people here, you can easily click a praise to encourage me to continue to create, creation is not easy, learn together, as soon as possible to achieve wealth freedom ~

The resources

  • Vue2.0 source

Recommended reading

  • Vue2.0 source code interpretation series (2) – open the merge option of Vue mystery gift box
  • Vue2.0 source code interpretation series – from Vue mysterious gift box