我正在参加「掘金·启航计划」
令人头疼的 mixin
众所周知,vue 的 mixins
是个非常灵活,但又很容易带来混乱的 API。不知道你是否像我一样,常常因为项目中用了 mixins
而感到头疼。比如说:
- 来回切换文件:有人会因为文件太大而把一部分内容提取到单独的
mixin
中,看起来文件变小了,但改代码的时候需要在多个文件之间不停切换 - 找不到函数和变量:模板中用到一个函数,在
methods
中搜索不到,然后发现组件引入了很多mixins
,不知道要该去哪里找
Mixins
本该是一种强大的重用代码的手段,但使用之后往往带来更多的混乱,代码变得不易维护。所以,在 Vue3 中,不再推荐使用 mixins
,而是改用 组合式 API 的方式来重用代码。
那么,我们是不是不应该在日常代码中使用 mixins
呢?我觉得也不是。Mixins
是个强大的工具,但本身存在一些严重的问题,如果我们能把 mixins
中存在的问题解决掉,那剩下的就会是:强大。
为什么会这样?
面对 mixins
这样灵活的工具,我们必须理解它的问题所在,才能避免在使用时带来混乱。
本质上,mixins
是一种重用代码的手段,它可以向一个对象中注入另一个对象中的所有属性。并且,一个对象可以同时从多个不同对象中注入属性,所以非常灵活。而这样的灵活导致了一些致命的问题:
- 来源不明:使用的
mixins
越多,越难找到某个属性或方法来自哪里 - 无法精确引入:引入一个
mixin
时,会引入这个mixin
的全部属性,因此很可能会引入一些用不到的东西,也会导致很难清楚知道到底引入了哪些属性和方法 - 命名冲突:引入多个
mixins
时,引入的属性名可能会冲突 - 耦合问题:不规范地使用
mixins
功能,可能会导致耦合问题,比如因为组件太大而抽离一部分属性到mixins
中,这样产生的mixins
一般会互相依赖,造成强耦合
作为对比,我们看看其他类似技术为什么没有这些问题:
- ES模块:一个模块可以导出很多东西,但我们可以选择性导入,这样就能清楚知道导入了哪些内容,并且导入的变量和函数可以重命名,这样就解决了命名冲突的问题
- Vue3组合式函数:Vue3 推荐使用组合式函数来替代
mixins
实现逻辑重用,组合式函数一般返回一个对象,解构时可以只取出对象中的部分属性,从而可以精确控制引入内容,并且解构语法支持自定义变量名,因此不会导致命名冲突
如果我们想要放心使用 mixins
功能,就必须解决以上问题。从 ES模块 和 Vue3组合式函数 的设计,我们可以看出一些关键点,那就是需要能 精确控制引入的内容,具体包括:
- 清晰看到引入的内容:在引入
mixins
的地方,可以直接看到引入了哪些属性和方法 - 支持部分引入:可以只引入需要用到的属性,而不是引入
mixin
对象上的全部属性 - 自定义引入的属性名:可以指定要引入的属性和方法的名字,避免命名冲突
这看起来似乎是不可能的,因为 mixin
就是一个对象,一旦引入就要接受这个对象上的所有属性和方法,也不可能在引入的时候去修改对象的属性名。
那还能不能用?
当然,你可以只使用生命周期钩子的功能,而不引入 data
、计算属性和方法,这样就不会往实例上添加额外属性,也就不会有这些问题。但是,这会使 mixins
的功能大打折扣。
幸运的是,确实有一个方法能够精确控制引入的内容,那就是动态创建 mixin
对象。
一般来说,我们习惯把 mixin
定义为一个对象,然后在组件中直接引入。但其实只要把它定义成一个函数,问题就会得到解决。
我们可以把 mixin
定义成一个函数,通过函数参数接收需要引入的属性名,然后生成并返回真正的 mixin
对象。这样的 mixin
就是动态生成的。引入时,调用这个函数,通过传入的参数就可以精确控制引入的内容,可以指定要引入哪些属性,也可以指定引入的属性名字。
import mouse from '@/mixins/mouse'
export default {
mixins: [
mouse({
mouseX: 'x',
mouseY: 'y'
})
]
}
我们给这种方法取一个不冲突的名字,就称为 动态Mixin 好了。
当然,这种写法增加了编写 mixins
的难度,所以我认为,mixins
适合用来引入一些简单的属性。比如,VueUse 中的那些工具方法就很适合使用动态 Mixin 来实现。
动态Mixin示例
接下来我列举一些自己在项目中经常使用的 mixins
,大家可以体会一下这种动态 Mixin 的适用场景以及带来的好处。
倒计时
倒计时是业务中经常会用到的一种场景,比如发送验证码时一般会显示倒计时。然后有些使用须知的弹窗,确认按钮也会在倒计时结束后才允许点击。
这种倒计时非常简单,只需要一个记录当前剩余时间的属性,加上一个设置倒计时时长的函数。这种场景就非常适合使用 mixins
来实现。实现过程先不考虑,使用效果是这样的:
<template>
<button :disabled="!!seconds" @click="setCountdown(60)">
{{ seconds || '发送验证码' }}
</button>
</template>
<script>
import countdown from '@/mixins/countdown';
export default {
// 引入 seconds 属性存储剩余秒数
// 引入 setCountdown 方法设置倒计时秒数
mixins: [countdown('seconds', 'setCountdown')],
};
</script>
这个 mixins
引入了一个属性和一个方法,引入的内容都是可以指定名字的。调用 setCountdown
方法时会设置 seconds
属性的值,并重新开始倒计时。
不过在 Vue 中,我们可以充分利用 Vue 自带的响应式特性。我们可以利用 watch
去进行监听,在检测到 seconds
变化时,重新开始倒计时。这样就可以少引入一个方法。
<template>
<button :disabled="!!seconds" @click="seconds = 60">
{{ seconds || '发送验证码' }}
</button>
</template>
<script>
import countdown from '@/mixins/countdown';
export default {
// 引入 seconds 属性记录倒计时秒数
mixins: [countdown('seconds')],
};
</script>
看,使用 mixins
之后,代码变得特别简洁清晰。而且只需要写一次,今后就可以在其他项目中直接使用这个 mixin
。而如果不使用 mixins
自己实现,就需要多写很多代码,完善的写法每次还需要考虑怎么清除定时器,防止内存泄漏。
这个 mixin
的具体实现可以看这里:github.com/web1706/vue…。你可以直接在你的项目中使用。
路由query参数
编写一个操作路由 query 参数的 mixin
其实是挺有必要的。
想象这样一个场景:页面上有一个 Tabs
组件,有多个页签。当我们切换页签时,一般会把当前激活的页签的值存储在 data
中。
export default {
data() {
return {
// 当前 tab 页
currentTab: 'one'
}
}
}
这样其实会有一个问题,就是刷新页面后,或者进入其他页面再返回时,是回不到之前的页签的。如果有一天需要改成刷新时保留激活的页签,最好的方法就是把页签值存入路由 query 中。这时如果有这样一个操作 query 参数的 mixin
就方便了。
import routeQuery from '@/mixins/route-query';
export default {
mixins: [
// 当前 tab 页
routeQuery('currentTab'),
],
};
这个 mixin
会从 this.$route.query
中读取对应的参数值,放到名为 currentTab
的属性中。并且这个属性是可写的!写入时会自动调用 this.$router.replace
去替换 query 中的参数值。
当然,你可能还需要指定默认值,这也没问题,只需要改成对象形式:
import routeQuery from '@/mixins/route-query';
export default {
mixins: [
routeQuery({
// 当前 tab 页
currentTab: {
default: 'one'
}
}),
],
};
或许,你想在 query 中使用一个不一样的参数名。
import routeQuery from '@/mixins/route-query';
export default {
mixins: [
routeQuery({
// 当前 tab 页
currentTab: {
from: 'tab'
},
// 简写形式
activeTab: 'tab'
}),
],
};
想要使用数字类型也没问题。
export default {
mixins: [
routeQuery({
// 对引入的属性值进行转换
companyId: {
type: Number,
},
// 简写形式
deptId: Number,
}),
],
};
可以看到,这个 mixin
非常灵活,功能也比传统 mixins
强大很多。
这个 mixin
的具体实现可以看这里:github.com/web1706/vue…。同样,你也可以把它用在你的项目中。
后记
本文介绍的这种使用函数动态生成 Mixin 的方式,大家觉得怎么样呢?大家平时又是如何看待和使用 Mixin 的呢?快来交流一下各自的想法吧。