preface

  • Project address in this chapter
  • throughVue.componentMethod creates a method based onObject.create(Vue.prototype)
  • Whether new Vue() has Componets to merge, and can be passed by (__proto__Chain call
  • Generate vNode virtual nodes and perform special processing on componentsData.hook = {init () {}}
  • Generate real DOM nodesnew Componet().$mount() -> vm.$el

To the chase

The sample

<div id="app">
    <my-button></my-button>
</div>

<script>
Vue.component('my-button', {
    template: '<button>hello</button>'
})

let vm = new Vue({
    components: {
        'my-button': {
            template: '<button>world</button>',
        }
    }
})
vm.$mount('#app')
</script>
Copy the code

Vue.component mergeOptions mergeOptions methods

/** stores global configuration */ with which each component is initialized
Vue.options = {}

/** Subclasses can find Vue */ via _base
Vue.options._base = Vue

Vue.options.components = {}
Vue.component = function (id, definition) {
    definition = this.options._base.extend(definition)
    this.options.components[id] = definition
}

/** Core method */
/** returns an object that inherits Vue class */
Vue.extend = function (opts) {
    const Super = this
    const Sub = function VueComponent(options) {
        this._init(options)
    }

    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub

    // Only merge with vue. options if global options have something to merge like vue. mixin...
    Sub.options = mergeOptions(Super.options, opts)

    return Sub
}

/** auxiliary methods **/
/ * * *@description Vue.com component hook function */
strats.components = function(parentVal, childVal) {
    let options = Object.create(parentVal)
    if (childVal) {
        for (let key in childVal) {
            options[key] = childVal[key]
        }
    }
    return options

}

/** Store various policies such as life cycle... * /
let strats = {}

/ * * *@description Merge the Vue. Options * /
export function mergeOptions(parent, child) {
    const options = {}

    for (let key in parent) {
        mergeField(key)
    }

    for (let key in child) {
        if (parent.hasOwnProperty(key)){
            continue
        }
        mergeField(key)
    }

    function mergeField(key) {
        let parentVal = parent[key]
        let childVal = child[key]

        if (strats[key]) {
            options[key] = strats[key](parentVal, childVal)
        } else {
            if(isObject(parentVal) && isObject(childVal)) { options[key] = { ... parentVal, ... childVal } }else {
                options[key] = parentVal || childVal
            }
        }

    }

    return options
}
Copy the code

Create a hook function to generate a VNode, add a new Ctor() component instance to the vnode, execute this hook function to generate a real DOM to return the component instance on the vNode

  1. Generate a vNode file
/ * * *@description Is it an object */
export function isObject(data) {
    return typeof data === 'object'&& data ! = =null
}

/ * * *@description Is it a native label */
export function isReservedTag(str) {
    let reservedTag = 'a,div,span,ul,li,p,img,button'
    return reservedTag.includes(str)
}

/ * * *@description Create a vnode */ for the label
export function createElement(vm, tag, data = {}, ... children) {
  / ** see if ** tag is a component
  if (isReservedTag(tag)) {
      return vnode(vm, tag, data, data.key, children, undefined)}else {
      const Ctor = vm.$options.components[tag]
      return createComponent(vm, tag, data, data.key, children, Ctor)
  }

}

/ * * *@description Create a vNode */ for the component
function createComponent(vm, tag, data, key, children, Ctor) {
  // The component is not a constructor
  if (isObject(Ctor)) {
      Ctor = vm.$options._base.extend(Ctor)
  }

  data.hook = {
      init(vnode) {
          let vm = vnode.componentInstance = new Ctor({_isComponent: true})
          vm.$mount()
      }
  }

  return vnode(vm, `vue-component-${tag}`, data, key, undefined.undefined, {Ctor, children})
}

/ * * *@description Create text vnode */
export function createTextElement(vm, text) {
  return vnode(vm, undefined.undefined.undefined.undefined, text)
}

/** Core method */
/ * * *@description Suit vnode * /
function vnode(vm, tag, data, key, children, text, componentOptions) {
  return {
      vm,
      tag,
      data,
      key,
      children,
      text,
      componentOptions
      / /...}}Copy the code

2. Generate the real DOM

export function patch(oldVnode, vnode) {
    // The component does not have an oldVnode
    if(! oldVnode) {return createElm(vnode)
    }

    if (oldVnode.nodeType == 1) {
        const parentElm = oldVnode.parentNode
        let elm = createElm(vnode)

        parentElm.insertBefore(elm, oldVnode.nextSibling)
        parentElm.removeChild(oldVnode);

        return elm
    }
}

/ * * *@description Create the real node of the component */
function createComponent(vnode) {
    let i = vnode.data
    if ((i = i.hook) && (i = i.init)) {
        i(vnode)
    }

    if (vnode.componentInstance) {
        return true}}/** Core method */
/ * * *@description Create the actual node element and assign it to el */ on vNode
function createElm(vnode) {
    let { tag, data, children, text, vm } = vnode
    if (typeof tag === 'string') {

        // Is not a component
        if (createComponent(vnode)) {
            return vnode.componentInstance.$el
        }

        vnode.el = document.createElement(tag)
        children.forEach(child= > {
            vnode.el.appendChild(createElm(child))
        })
    } else {
        vnode.el = document.createTextNode(text)
    }
    return vnode.el
}
Copy the code