开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第23天,点击查看活动详情
我们通常会遇到这么一个场景:有几个基本功能一样的组件,但是他们之间又存在着足够的差异。这时候你就来到了一个岔路口:我是把他们“按部就班”地写成不同的组件呢?还是保留为一个“公共组件”,然后通过props传参进行不同功能之间的区分呢?
现在还有一个场景:在一些组件(甚至是项目中全部和某个功能有关的组件)中,有某个功能是相同的。而且都需要利用这个功能进行后续操作。你又需要选择了,但是这次有一个前提:肯定是要“复用”的 —— 公共组件?还是 mixin ?
我们现在来分析下:
在第一个场景中,其实两种解决方案都不够完美:如果拆分成多个组件,你就不得不冒着一旦功能变动就要在所有相关文件中更新代码的风险,这违背了 DRY 原则;反之,太多的 props 传值会让代码变得混乱不堪,后续难以维护、团队理解困难,效率降低。
使用Mixin
Vue 中的 Mixin 对编写函数式风格的代码很有用,因为函数式编程就是通过减少移动的部分让代码更好理解。Mixin 允许你封装一块在应用的其他组件中都可以使用的函数。如果使用姿势得当,他们不会改变函数作用域外部的任何东西。因此哪怕执行多次,只要是同样的输入你总是能得到一样的值。
mixin其实有两种形式 —— Object 和 Function。
它们都可以在单个组件或者全局中引用。但对于function形式的mixin,笔者更推荐使用在单个组件中。
先看第一种形式:
假设有一对不同的组件,它们的作用是通过切换状态(Boolean类型)来展示或者隐藏模态框或提示框。这些提示框和模态框除了功能相似以外,没有其他共同点:它们看起来不一样,用法不一样,但是逻辑一样。
这时我们可以将它们的公共逻辑部分封装为一个js文件:
// mixins目录下的toggle.js文件
export const toggle = {
data() {
return {
isShowing: false
}
},
methods: {
toggleShow() {
this.isShowing = !this.isShowing;
}
}
}
一般我们选择新建一个专门的mixin目录。在里面创建一个文件含有.js扩展名,为了使用Mixin我们需要输出一个对象。(es6 Modules)
然后使用mixins:[] 的方式引入mixin文件,(引入后)可直接使用(就像开头说的“插件”一样):
import { toggle } from './mixins/toggle';
//...
const Modal = {
template: '#modal',
mixins: [toggle],
//...
};
const Tooltip = {
template: '#tooltip',
mixins: [toggle],
//...
};
第二种形式:
这种形式其实就特别适用于开头说的第二种情况。因为 mixin 内部也相当于一个组件,所以一个组件该有的它都具备。而且上面也说了:当mixin被引入后它内部的东西可以被直接使用 —— 其实就是被merge到引用它的组件中了!(相当于对父组件的扩展)
假如我们请求完要根据数据给出提示并且要给出降级方案(默认提示)。这个需求基本是项目中必不可少而且不止一次出现的。但是像一般情况没有引用其余外部UI而且又不是“通用提示”放在全局中不太合适。这时候就需要我们的 mixin 出场了:
// mixins目录下的index.js文件
function formatRes(res) {
const data = res.data;
if (data.status.code === 0) {
return data
} else {
if (判断是否引入了提示框组件) {
//提示框组件的调用和传参
}
return data
}
}
var mixin = function (options) {
let defaultData = {}
let defaultMethods = {}
defaultMethods.formatRes = formatRes;
return {
data: function () { //这个会在引用它的组件的data中出现
return defaultData;
},
methods: defaultMethods, //同上,在引用它的组件中可直接通过this.formatRes调用到
}
}
export default mixin
因为是函数形式,所以在引用vue的script开头应该这么写:
import mixin from '../mixin/index'
const mixinCommen = mixin()
使用:
const res = await this.$http({ //封装的请求库
method: 'GET',
url: '请求地址',
params: {
param: {
}
}
})
const {result} = this.formatRes(res)
if(result) {
this.areaList = result
} else {
//...
return false
}
return true