深刻浅出Vue.extend(源码导读+实现一个编程式组件)

简介

Vue.extend做为一个全局api,固然值得咱们去深刻学习的,同时也是实现编程式组件的重要途径,因此咱们经过源码导读加实践的方式开始吧。首先咱们会带着几个问题来进行学习,若是你都不会,哈哈哈恭喜你,学完本篇你就会明白了。html

  • Vue.extend在Vue当中起到的做用?
  • 讲讲Vue.extend内部实现?
  • 实现一个编程式组件,说说具体思路?

目录

  • 基本用法
  • 源码赏析
  • 源码导读
  • 手动实现一个编程式组件
  • 总结

基本用法

参数:

{Object} optionsvue

用法:

使用基础 Vue 构造器,建立一个“子类”。参数是一个包含组件选项的对象。node

data 选项是特例,须要注意 - 在 Vue.extend() 中它必须是函数。编程

<div id="mount-point"></div>
复制代码
// 建立构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 建立 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')
复制代码

结果以下:element-ui

<p>Walter White aka Heisenberg</p>
复制代码

源码赏析

在咱们赏析源码以前,咱们首先确定要找到这个api及调用的位置,这样才能更好的理解它的做用。经过官方用法咱们就知道,它确定和组件有关系,那么咱们很容易找到在源码中建立组件的位置,接下来咱们一步一步找:api

1建立组件:在src/core/vdom/create-element:(113)

vnode = createComponent(Ctor, data, context, children, tag)
复制代码

2进入createComponent函数:在src/core/vdom/create-component:(115)

if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
复制代码

这里, baseCtor其实就是Vue,具体缘由在之后分析组件源码会讲解,这里不做研究。咱们终于找到了Vue.extend调用的位置,就是在Vue建立每一个组件的时候调用,经过构造器建立子类。下面就是完整的源码:缓存

Vue.cid = 0
  let cid = 1
Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    const Sub = function VueComponent (options) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub } } 复制代码

接下来咱们就一步一步完全搞懂源码。bash

源码导读

a:

extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
复制代码

首先,extendOptions使咱们传入进去的模板,这里面的this就是调用extend的对象,就是Vue,而后保存在变量Super中,变量SuperId就保存Vue中的惟一标识(每一个实例都有本身惟一的cid).app

b:

const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
复制代码

这一段做为缓存策略的,放在下面说。dom

c:

const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }
复制代码

用一个name变量来保存组件的名字,就是我写组件时候的name,若是没写就使用父组件的name,而后对name经过validateComponentName函数验证,主要就是判断name不能是html元素和不能是非法命名。

d:

Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
复制代码

上面咱们建立一个子类Sub,这里咱们经过继承,使Sub拥有了Vue的能力,而且添加了惟一id(每一个组件的惟一标识符)

e:

Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super
复制代码

这里调用了mergeOptions函数实现了父类选项与子类选项的合并,而且子类的super属性指向了父类。

f:

if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }
复制代码

初始化了props和computed.

g:

// allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
复制代码

将父类的方法复制到子类,包括extend,mixin,use,component,directive,filter.还新增属性superOptions,extendOptions,sealedOptions 。

h:

// cache constructor
    cachedCtors[SuperId] = Sub
复制代码

以前的代码结合,将父类的id保存在子类的属性上,属性值为子类,在以前会进行判断若是构造过子类,就直接将父类保存过的id值给返回了,就是子类自己不须要从新初始化,,做为一个缓存策略。

整体来讲,其实就是建立一个Sub函数并继承了父级。

手动实现一个编程式组件

咱们在通常调用组件时,都会先组件注册,再在模板中引用。一个两个仍是能够接受的,那么若是这个组件不少不少呢,因此每次注册应用就显得力不从心了。用过element-ui咱们都知道,能够不用注册,直接经过命令调用:

this.$message.success('成功了')
复制代码

是否是特别特别方便,简单明了,并且还符合编程思惟。接下来咱们将手动实现一下:

a:建立一个组件(用于编程式调用的)

<template>
  <div class='toast'
       v-show='isShow'>
    {{message}}
  </div>
</template>
<script>
export default {
  data () {
    return {
      message: '',
      isShow: false
    }
  },
  methods: {
    show (message, duration) {
      this.message = message
      this.isShow = true
      setTimeout(() => {
        this.isShow = false
        this.message = ''
      }, duration)
    }
  }
}
</script>
<style scoped>
.toast {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 999;
  padding: 8px 10px;
  background-color: rgba(0, 0, 0, 0.3);
}
</style>
复制代码

这个组件很是简单,两个变量分别是显示的消息和是否显示。我还定义了一个方法,做用就是使模板取消隐藏,而且传入一个duration参数经过定时器表示显示的时间,时间一到isShow就变false,能够说是很是简单了,模板有了,下面开始重点了。

b:实现编程式

import Toast from './toast'
const obj = {}
obj.install = function (Vue) {
  // 建立构造器
  const ToastContrystor = Vue.extend(Toast)
  // new的方式 根据组件构造器,能够建立组件对象
  const toast = new ToastContrystor()
  // 手动挂载某一个元素上
  toast.$mount(document.createElement('div'))
  // toast.$el对应的就是div
  document.body.appendChild(toast.$el)
    //组件挂载到Vue原型上
  Vue.prototype.$toast = toast
}
export default obj
复制代码

把组件看成插件同样,经过Vue.use()使用,注释都写的很是清楚了,一步一步理解。

c:在main.js注册

import Vue from 'vue'
import toast from '@/components/common/toast/index.js'
Vue.use(toast)
复制代码

d:使用

this.$toast.error('注册失败,请从新输入', 1000)
复制代码

就能够在项目各个地方调用了。咱们就实现了一个编程式的组件。

总结

VUe.extend整体来讲其实就是建立一个类来继承了父级,顶级必定是Vue.这个类就表示一个组件,咱们能够经过new的方式来建立。学习了extend咱们就很容易的实现一个编程式组件。

相关文章
相关标签/搜索