vue2核心原理(简易) - 组件(Vue.componet)的实现笔记

528 阅读1分钟

前言

  • 本章项目地址
  • 通过Vue.component方法 创建一个方法 基于Object.create(Vue.prototype)
  • new Vue() 是否存在componets进行合并,并可通过(__proto__)链式调用
  • 生成vnode虚拟节点 并对组件进行特殊处理 data.hook = { init(){} }
  • 生成真实的dom节点 new Componet().$mount() -> vm.$el 图片替换文本

正题

示例

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

Vue.component
mergeOptions 合并options方法

/** 存放全局的配置 每个组件初始化的时候 都要与其合并 */
Vue.options = {}

/** 子类可以通过_base, 找到Vue */
Vue.options._base = Vue

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

/** 核心方法 */
/** 给个对象 返回继承Vue的类 */
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

    // 只与Vue.options 合并 如果全局options有东西进行合并 如Vue.mixin...
    Sub.options = mergeOptions(Super.options, opts)

    return Sub
}

/** 辅助方法 **/
/**
 * @description Vue.componet 组件的hook函数
 */
strats.components = function(parentVal, childVal) {
    let options = Object.create(parentVal)
    if (childVal) {
        for (let key in childVal) {
            options[key] = childVal[key]
        }
    }
    return options

}

/** 存放各种策略 如生命周期... */
let strats = {}

/**
 * @description 合并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
}

生成vnode时创建hook函数,并在vnode上 加new Ctor()组件实例, 在生成真实的dom时 执行此hook函数 返回vnode上的组件实例.$el

  1. 生成vnode文件
/**
 * @description 是否是对象
 */
export function isObject(data) {
    return typeof data === 'object' && data !== null
}

/**
 * @description 是否是原生标签
 */
export function isReservedTag(str) {
    let reservedTag = 'a,div,span,ul,li,p,img,button'
    return reservedTag.includes(str)
}

/**
* @description 创建标签的vnode
*/
export function createElement(vm, tag, data = {}, ...children) {
  // **看这里** tag是不是组件
  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 创建组件的vnode
*/
function createComponent(vm, tag, data, key, children, Ctor) {
  // 组件是不是构造函数
  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 创建文本的vnode
*/
export function createTextElement(vm, text) {
  return vnode(vm, undefined, undefined, undefined, undefined, text)
}

/** 核心方法 */
/**
* @description 套装vnode
*/
function vnode(vm, tag, data, key, children, text, componentOptions) {
  return {
      vm,
      tag,
      data,
      key,
      children,
      text,
      componentOptions
      // .....
  }
}

2.生成真实的dom

export function patch(oldVnode, vnode) {
    // 组件没有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 创建组件的真实节点
 */
function createComponent(vnode) {
    let i = vnode.data
    if ((i = i.hook) && (i = i.init)) {
        i(vnode)
    }

    if (vnode.componentInstance) {
        return true
    }
}

/** 核心方法 */
/**
 * @description 创建真实的节点元素 并赋值与vnode上el
 */
function createElm(vnode) {
    let { tag, data, children, text, vm } = vnode
    if (typeof tag === 'string') {

        // 是不是组件
        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
}