Vue3 Composition API如何替换Vue Mixins
mixin 模式表面上看起来很安全。然而,通过合并对象来共享代码会增加脆弱性,并掩盖功能的来源。
Composition API 允许 Vue 借助普通 JavaScript 的函数参数、返回值和模块系统共享代码。Vue 3 仍然支持 mixins 以便迁移,但新代码更推荐 composables(组合式函数)。
回顾 Mixins
一个普通组件可能这样定义:
// MyComponent.js
export default {
data() {
return { myLocalDataProperty: null }
},
methods: {
myLocalMethod() {}
}
}
把公共属性提取到 mixin:
// MyMixin.js
export default {
data() {
return { mySharedDataProperty: null }
},
methods: {
mySharedMethod() {}
}
}
组件通过 mixins 使用它:
// ConsumingComponent.js
import MyMixin from './MyMixin.js'
export default {
mixins: [MyMixin],
data() {
return { myLocalDataProperty: null }
},
methods: {
myLocalMethod() {}
}
}
运行时得到的效果近似于:
export default {
data() {
return {
mySharedDataProperty: null,
myLocalDataProperty: null
}
},
methods: {
mySharedMethod() {},
myLocalMethod() {}
}
}
Mixins 的缺点
命名冲突与合并规则
const mixin = {
data() {
return { myProp: 'mixin' }
}
}
export default {
mixins: [mixin],
data() {
return { myProp: 'component' }
}
}
对象选项发生同名冲突时通常以组件选项优先;同名生命周期钩子不会覆盖,而是合并成数组,mixin 钩子先执行。自定义选项可以配置 merge strategy。冲突有时不会直接报错,却会静默覆盖,让多个 mixin 和第三方包的来源难以追踪。
隐式依赖
组件可以直接使用 mixin 数据,mixin 也可能假定组件上存在某个属性,例如校验 mixin 假设组件定义了输入值。重命名组件字段时,编辑器和 linter 不一定知道它破坏了 mixin,往往到运行时才暴露。
快速入门 Composition API
Vue 2 计数器:
export default {
data() {
return { count: 0 }
},
computed: {
double() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
}
Vue 3 Composition API:
import { computed, ref } from 'vue'
export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
}
}
ref 本身是带 .value 的对象包装器,但它可以包装 primitive;包装对象时,对象会被深层转换。模板会自动解包顶层 ref,JavaScript 中则必须使用 count.value。
提取并复用 composable
// useCounter.js
import { computed, ref } from 'vue'
export function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
}
// MyComponent.js
import { useCounter } from './useCounter.js'
export default {
setup() {
const { count, double, increment } = useCounter()
return { count, double, increment }
}
}
状态定义在函数调用内部,因此每个组件实例、每次调用都有独立状态。若有意共享全局状态,应明确使用模块级状态或 Pinia,而不是把 composable 误当成自动的全局 store。
命名冲突变成普通 JavaScript 问题
Composition API 不会自动消灭冲突,但返回值可显式改名:
const { count: localCount } = useCounter()
const { count: remoteCount } = useRemoteCounter()
这比 mixin 的实例选项合并更容易追踪,冲突也会按普通 JavaScript 绑定规则暴露。
依赖更显式,但并非绝对没有隐式依赖
import { ref } from 'vue'
import { useValidation } from './useValidation.js'
export default {
setup() {
const inputValue = ref('')
const { errors, validate } = useValidation(inputValue)
return { inputValue, errors, validate }
}
}
输入通过参数传入、能力通过返回值导出,契约更明确。不过 composable 仍可能依赖导入的模块、provide/inject 或全局服务,所以不能宣称隐式依赖完全不存在。
生命周期与副作用清理
从 mixin 迁移时,不要遗漏监听器清理:
import { onMounted, onUnmounted, ref } 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 }
}
composable 应在 setup() 或 <script setup> 中同步调用,使生命周期与当前组件实例正确关联。
总结
mixin 的主要问题是命名冲突、数据来源不清晰、隐式依赖和不直观的合并规则。Composition API 通过函数、模块、参数和返回值把这些关系显式化,但它不是万能方案;Options API 仍然可用,Vue 3 也仍支持 mixins。对于新逻辑复用,更推荐 composables。
官方参考
作者:独立开发者张张
原文链接:https://juejin.cn/post/6844904136065056781
来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。