by yugasun from yugasun.com/post/you-ma… 本文可全文转载,但须要保留原做者和出处。html
咱们在实际开发过程当中,当项目愈来愈大,组件愈来愈丰富时,常常会面临一个问题:不少组件会公用一些通用的 props
、data
和 methods
等声明,可是也会掺杂组件本身的一些私有特有声明,那么咱们能不能像类的继承同样,来提炼和继承呢? 固然这是能够的,这里能够经过两个基本 API extends 和 mixins 来实现。这两个API是能够相互替换的,惟一的区别是,extends
属性接受的一般是个单一组件对象,而 mixins
属性接受的是个组件对象数组。当他们只继承单一组件时,是能够互换的。因为本人开发中,习惯使用 mixins
,因此本文全部实例均使用 mixins
来实现。vue
先来看看官方介绍:git
mixins
选项接受一个混入对象的数组。这些混入实例对象能够像正常的实例对象同样包含选项,他们将在 Vue.extend() 里最终选择使用相同的选项合并逻辑合并。举例:若是你的混入包含一个钩子而建立组件自己也有一个,两个函数将被调用。github
简单的理解就是Vue实例中的全部属性配置能够经过 mixins
实现继承。api
简单示例以下:数组
var mixin = {
created: function () { console.log(1) }
}
var vm = new Vue({
created: function () { console.log(2) },
mixins: [mixin]
})
// => 1
// => 2
复制代码
假设有这么个需求: 在某个组件渲染后向服务器端发送一个请求,进行打点
,好的,很快咱们想到 mounted
钩子函数,而后快速的实现了需求,代码以下:服务器
export default {
name: 'comp1',
// ...
mounted() {
console.log('Component comp1 mounted');
}
// ...
}
复制代码
而后某一天需求变成了 某几个 组件须要进行打点
,好的,咱们又进行了一顿猛如虎的操做,将上面代码复制到每一个打点的组件,很快把需求搞定了。但是噩梦才刚刚开始,过了几天需求又变了,除了在组件渲染后须要打点,同时还须要在 created
后打点..... 此种场景是否是像极了爱情
,面对现实咱们老是在不停屈服,最终仍是忍痛把需求作了。函数
回头冷静思考下,其实这个打点是很广泛的需求。若是从头来过,咱们必定会选择用继承的方式来实现,而不是盲目的去爱,哦不,盲目的复制粘贴。由于咱们有 mixins
,只须要编写一次,处处可用。那就让咱们从头再来一次,首先建立一个 src/minins/log.js
文件:post
export default {
created() {
console.log(`Component ${this.$options.name} created.`);
},
mounted() {
console.log(`Component ${this.$options.name} mounted.`);
},
};
复制代码
而后在你须要的任何一个组件中引入使用:性能
import logMixin from '@/mixins/log';
export default {
name: 'comp1',
mixins: [logMixin],
// ...
}
复制代码
一番修改后,你会发现产品经理妹子也能够那么迷人,是否是你又开始相信爱情了......
运行项目,打开控制台输出以下:
Component comp1 created.
Component comp2 created.
复制代码
上面的需求是组件打点,如今咱们新增了需求,须要给某几个组件添加一个通用方法 sayHello
到 methods
中,并在组件渲染后调用,可是只是上面打点的部分组件须要添加此功能,虽然只是部分组件,但也有个上百个吧(夸张手法,切勿模仿
)。听到这里,你默默推开了身边的产品妹子,拒绝道:对不起,我已经不相信爱情了
。此时,有个声音在轻声的嘀咕着:你还能够相信的!
。
好的,那么,我就再让你相信一次。首先添加文件 src/mixins/func.js
:
export default {
mounted() {
this.sayHello();
},
methods: {
sayHello() {
console.log('产品妹子,你好美!');
},
},
};
复制代码
而后在须要的组件中引入就好了:
import logMixin from '@/mixins/log';
import funcMixin from '@/mixins/func';
export default {
name: 'comp1',
mixins: [logMixin, funcMixin],
// ...
}
复制代码
运行项目,打开控制台输出以下:
Component comp1 created.
Component comp2 created.
Component comp1 mounted.
产品妹子,你好美!
Component comp2 mounted.
复制代码
好了,你终于能够跟产品妹子一块儿在夕阳下愉快地奔跑了。忽然有一天,组件渲染后打点,成了公司的规范
,也就是你编写的全部组件都须要打点了,产品妹子很无奈的看着你说:这不是我想要的结果,是你作的太优秀,被公司提上了日程,写入了编码规范
.....可现实就是这样,你总想逃,却逃不掉
......
其实你还能够逃的,Vue.mixin 说。
全局注册一个混入,影响注册以后全部建立的每一个 Vue 实例。插件做者可使用混入,向组件注入自定义的行为。不推荐在应用代码中使用。
这不就是你一直追寻的爱情吗?因而你移除了以前引入的 logMixin
,而后默默地在入口文件(src/main.js
)中写下了爱情的宣言:
//...
Vue.mixin({
created() {
console.log(`Component ${this.$options.name} created from 全局打点`);
},
mounted() {
console.log(`Component ${this.$options.name} mounted from 全局打点`);
},
});
// new Vue....
复制代码
运行项目,打开控制台输出以下:
Component undefined created from 全局打点
Component App created from 全局打点
Component Index created from 全局打点
Component router-link created from 全局打点
Component comp1 created from 全局打点
Component comp1 created.
Component comp2 created from 全局打点
Component comp2 created.
Component comp3 created from 全局打点
Component router-link mounted from 全局打点
Component comp1 mounted from 全局打点
Component comp1 mounted.
产品妹子,你好美!
Component comp2 mounted from 全局打点
Component comp2 mounted.
Component comp3 mounted from 全局打点
Component Index mounted from 全局打点
Component App mounted from 全局打点
Component undefined mounted from 全局打点
复制代码
你会发现全部的 Vue 组件都注入了打点。
其实 mixins
用起来很是简单,可是其背后的原理,仍是值得咱们去深究的:
mixins
后,钩子函数是依次执行的,而不是替换?mixins
后,自身 data
属性优于混入属性?要想回答上面的问题,咱们得从 vue 源码开始提及。
Vue 在初始化 mixin
的时候,对于不一样的属性,采用的策略是不一样的,初始化代码在文件 src/core/global-api.js 中, 以下:
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
Vue.options = mergeOptions(Vue.options, mixin)
}
}
复制代码
你会发现是经过 mergeOptions
函数来进行合并的,它在文件 src/core/util/options.js, 它的源码以下:
/** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */
export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object {
// 省略没必要要代码
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
}
复制代码
这个函数很好理解,大概作的事情就是将 child
的属性合入到 parent
中,不一样属性采用了不一样的策略,这些策略都定义在 strats
对象上。
咱们先看看 生命周期函数
的合并策略,代码以下:
/** * Hooks and param attributes are merged as arrays. */
function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
复制代码
能够发现 Vue 实例的生命周期函数最终都赋值成了一个数组,并对 mixins
中的进行了数组合并。这就是为何组件 mixins
后的生命周期函数是依次执行的缘由。
一样再来看看 data
的合入策略:
/** * Helper that recursively merges two data objects together. */
function mergeData (to: Object, from: ?Object): Object {
let key, toVal, fromVal
for (key in from) {
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isObject(toVal) && isObject(fromVal)) {
mergeData(toVal, fromVal)
}
}
return to
}
复制代码
这个过程就是对象属性的合并,可是 to
上的优先级是高于 from
的,这就是为何咱们在对一个组件进行 mixins
的时候,自身 data
优先级高于混入的 data
属性,也就是若是 mixins
中和自身均含有相同属性时,混入属性值不会被添加到当前组件中。
感兴趣的同窗,还能够去研究下其余属性的混入策略,源码均在 src/core/util/options.js 中,也很好理解。
越是简单的东西,越是把双刃剑,实际使用中必定要注意,特别是全局性的混入,这会带来性能开销。你们能够多编写,多总结,找到最合适的使用习惯就好,建议多阅读著名开源项目的源码,你会从中学到更多前辈们的技巧。