为什么 Mixin 被认为是有害的
Mixin
是在 Vue 2
中引入的,作为组件之间共享代码的解决方案,这种方式成为许多代码库不可或缺的一部分。然而,随着时间的推移,它们的使用开始出现问题。尽管 mixins
最初很有吸引力,但现在许多开发人员认为它是有害的。在本文中,我们将探讨Mixin
的缺点和更好的共享代码的方法。
缺点
复杂
Mixin
可以向组件添加过多的逻辑,使其变得更加复杂且难以理解,从而导致意想不到的后果和维护困难。例如,如果一个组件有多个 mixin
,每个 mixin
都有自己的属性和方法,那么该组件将有很多逻辑,并且很难确定每个属性和方法如何影响组件的行为。由于混入而导致组件过于复杂,使得扩展或调试变得具有挑战性。
为了避免这种情况,明智地选择 mixins
并确保它们简化而不是使得组件的逻辑复杂化至关重要。Mixin
会给组件带来偶然和本质的复杂性。偶然复杂性是指在不影响功能的情况下可以消除的不必要的复杂性,而本质复杂性是解决问题所固有的,无法避免。使用 mixins
时考虑这两种类型的复杂性非常重要。
命名冲突和模糊的合并策略
Mixin
可能与组件具有重叠的属性,从而导致命名冲突和意外行为。
比如说我们有一个包含特定员工逻辑的mixin
,其中包括声明增值税金额。
// mixins/employee.js
export default {
data() {
return {
vat: 20
};
},
computed: {
calculateSalary() {
return this.baseSalary - (this.baseSalary * this.vat) / 100;
}
}
};
以及还声明了增值税金额的薪资显示组件:
<template>
<div id="app">工资: {{ calculateSalary }}</div>
</template>
<script>
import employee from "./mixins/employee.js";
export default {
name: "App",
mixins: [employee],
data() {
return {
baseSalary: 100000,
vat: 30,
};
},
};
</script>
在这种情况下,计算函数里声明的增值税金额(vat
)将被忽略,并且在具有更复杂逻辑的现实示例中,这很难发现,并且如果我们对 mixin
进行隔离单元测试,可能会得到误报。
当两个不同 mixins
之间的命名冲突时,这可能会更加令人困惑。在这种情况下,最后声明的一个会覆盖另一个!
可重用性降低
Mixins
使得重用组件变得更加困难,因为它们增加了组件的依赖性并使其与特定功能的耦合更加紧密。
例如,想象一个使用 mixin
的组件,该 mixin
为该组件提供了特定的样式。如果另一个组件也想使用相同的样式,则它必须使用相同的 mixin
。这意味着组件的样式与现在的 mixin
紧密耦合,并且如果不使用 mixin
则无法重用。
这使得组件的可重用性降低,因为如果不包含 mixin
及其依赖项,它就无法在应用程序的其他部分中使用。这可能会导致代码重复并使应用程序更难以维护。
解决方案
组合式api
组合式api 是 Vue 3
的亮点。主要优点是以可组合函数的形式实现干净、高效的逻辑重用。它解决了 mixin
的缺点。社区里有非常多使用该方式的项目,例如VueUse
。此外,它还是一种干净的机制,可以轻松地将有状态的第三方服务或库集成到 Vue
的响应性系统中,例如不可变数据、状态机和RxJS。
前面提到了使用 mixin
会创建高度耦合且难以扩展或维护的代码:
另一种方法是让每个组件显式导入其所有依赖项。这样,一切都是透明的并且易于推理。设计上消除了命名冲突,并且测试也更加简单。
实际上,Composition API
是一组实用帮助程序,使我们能够使用显式导入的函数而不是声明选项来编写组件。它是一个涵盖以下 API
的总称:
Reactivity API
(响应式api):例如ref()
和reactive()
,它允许我们直接创建反应状态、计算状态和观察者。Lifecycle Hooks
(生命周期钩子):例如onMounted()
和onUnmounted()
,它允许我们以编程方式挂钩到组件生命周期。- 依赖注入,即
provide()
和inject()
,它允许我们在使用Reactivity API
时利用Vue
的依赖注入系统。
简单示例:
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>鼠标位置: {{ x }}, {{ y }}</template>