结合 Vue 官网,深度了解 Vue.extend / extends / mixins

很多人使用 Vue 很久了,其实还分不清 Vue.extend / extends / mixins 到底有什么区别,或者分别在什么场景下来使用它们。这里我们可以结合官网的详细介绍及一些实际项目中常用场景来分别对他们深度探索一下

一、先看 Vue.extend( options )

官网介绍如下:

参数:
{Object} options
用法:
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
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 实例,并挂载到一个元素上。[关键点]注意:这里挂载到实际的 Dom 节点上,其实可以空挂载的,如下面的应用实例
new Profile().$mount('#mount-point') // 注意:当执行了 new Profile() 时,就已经执行了 Vue.created 这个勾子了,但还未执行 $mount 需自己再执行

结果如下:

<p>Walter White aka Heisenberg</p>

项目实际应用场景:

这里以创建一个全局 toast 组件来举例,源码地址:https://github.com/nelsonkuang/vue-pix2rem/blob/master/src/plugins/toast/index.js点击查看在线效果

import Toast from './toast.vue' // 引入vue模板
export default {
  install (Vue, options = {}) {
    // vue 的 install 方法,用于定义 vue 插件
    // 如果 toast 还在,则不再执行
    if (document.getElementsByClassName('toast').length) {
      return
    }
    let ToastTpl = Vue.extend(Toast) // 创建 vue 构造器
    // el:提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 css 选择器,也可以是 HTMLElement 实例。
    // 在实例挂载之后,可以通过 $vm.$el 访问。
    // 如果这个选项在实例化时有用到,实例将立即进入编译过程。否则,需要显示调用 vm.$mount() 手动开启编译(如下)
    // 提供的元素只能作为挂载点。所有的挂载元素会被 vue 生成的 dom 替换。因此不能挂载在顶级元素 (html, body) 上
    // let $vm = new ToastTpl({
    //   el: document.createElement('div')
    // })
    let $vm = new ToastTpl() // 实例化 vue 实例
    // 此处使用 $mount 来手动开启编译。用 $el 来访问元素,并插入到 body 中
    let tpl = $vm.$mount().$el // [关键点]注意:这里是的技巧是,空挂载,没有挂到任何 Dom 节点上,类似于 原生的 Dom 操作中的 HTML DOM createElement() 方法来创建一个节点对象
    document.body.appendChild(tpl) // [关键点] 类似于原生的 Dom 操作中的 HTML DOM createElement() 方法创建完节点后需要 append 到实际的 Dom 上

    Vue.prototype.$toast = {
      // 在Vue的原型上添加实例方法,以全局调用
      show (options) {
        // 控制toast显示的方法
        if (typeof options === 'string') {
          // 对参数进行判断
          $vm.text = options // 传入props
        } else if (typeof options === 'object') {
          Object.assign($vm, options) // 合并参数与实例
        }
        $vm.isShowIcon = !!options.isShowIcon // 是否显示icon
        $vm.show = true // 显示toast
      },
      hide () {
        // 控制toast隐藏的方法
        $vm.show = false
      }
    }
  }
}

总之:这是面向对象的思想(OOP),使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。使用时需要先使用 new 进行实例化,然后如果需要挂载(包挂空挂载,如上面的场景)形成 Dom / Virtual Dom 节点,需要执行 $mount()

二、关于 extends

官网介绍如下:

类型:Object | Function
详细:
允许声明扩展另一个组件 (可以是一个简单的选项对象构造函数),而无需使用 Vue.extend(基于基础 Vue 构造器)。这主要是为了便于扩展单文件组件
这和 mixins 类似。
示例:

var CompA = { ... }

// 在没有调用 Vue.extend 时候继承 CompA
var CompB = {
  extends: CompA,
  ...
}

项目实际应用场景:

其实这是典型的面向对象思想,项目实际场景,比如自己一开始写了个 Select 组件,现在需求有变,需要新增加一种新的带查找功能的 Select 组件姑且叫 SearchSelect,那我们可以通过 extends 的方式来基于 Select 组件,新建一个 SearchSelect 组件,如下:

<template>
<div>
    SearchSelect
</div>
</template>
<script>
import Select from './components/Select'
export default {
  name: "SearchSelect",
  data () {
		return {
		}
  },
  created(){
  },
  extends: Select  // 使用 extends 把 Select 的选项及方法全部扩展过来
}
</script>
<style lang="css" scoped>
</style>

总之:这也是面向对象的思想,使用 extends 把 目标组件 / 选项对象 / 构造函数 的选项及方法全部继承过来。但有个问题,如果我要把模版(template)也要继承可以怎么做呢?可以在 render(h) 函数据里面调用 this.$options.extends.render.apply(),参考:vue extends 页面模版

三、关于 mixins

官网介绍如下:

类型:Array<Object>
详细:
mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。
Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
示例:

var mixin = {
  created: function () { console.log(1) }
}
var vm = new Vue({
  created: function () { console.log(2) },
  mixins: [mixin]
})
// => 1
// => 2

更详细的混入介绍可以参考:自定义选项合并策略

这是一种相对较新面向切面(AOP)的编程思想。实际项目中使用的场景太多了,建议多用就是了,比如如果多个组件中使用的 props 参数、data 的状态变量、methods 的方法、生命周期的勾子函数有一样的话,都可以抽取出来放在一个共同的 mixin 文件里,然后大家引用同一个 mixin 文件就可以了。

总而言之

按需使用吧,按面向对象还是面向切面来选择吧。比如如果想要创建一个类似 toast 这个的全局空挂载的组件的实例,可以参考上面的 toast 插件,使用 Vue.extend(options) 来实现;如果想要继承某个组件来创建新的组件,可以使用 extends ,但要注意模版继承的特殊写法。最后,建议大量使用 minxin 把大量的共同的东西抽象出来,毕竟人生苦短哈哈,一样的东西重复维护就是浪费生命。