Vue 3 混入完全指南
本文档从零开始讲解 Vue 3 的混入(Mixins):什么是混入、如何定义和使用、选项如何合并、全局混入,以及混入的局限和 Vue 3 更推荐的组合式函数(Composables)替代方案,配有示例,适合新手系统学习。
一、什么是“混入”?
1.1 为什么需要“混入”?
多个组件里经常有相同的逻辑:相同的数据、相同的方法、相同的生命周期代码。
例如:很多页面都要“打印当前时间”“在挂载时请求用户信息”“共享一个格式化日期的函数”。
如果每个组件都复制粘贴一遍,会很难维护;把公共部分抽出去、再“混”进组件,就是混入在做的事。
1.2 混入是什么?
混入(Mixin) 是一个普通 JavaScript 对象,里面可以写和组件选项一样的东西:data、methods、computed、watch、生命周期钩子等。
组件通过 mixins 选项把这个对象“混进来”,Vue 会把混入里的选项和组件自己的选项按一定规则合并,就像把这些逻辑“复制”进当前组件一样。
1.3 重要说明:Vue 3 更推荐什么?
在 Vue 3 中,组合式 API(Composition API) 和 组合式函数(Composables) 是官方更推荐的方式来实现“逻辑复用”。
混入 仍然支持,但在 里不能直接使用 mixins,混入主要用在选项式 API(Options API) 的组件里。
学习混入有助于:
- 阅读或维护仍在使用选项式 API 和 mixins 的老项目。
- 理解“逻辑复用”的演进:混入 → 组合式函数。
本文会先讲清混入的用法和合并规则,再说明其局限,并简要介绍如何用组合式函数替代。
二、如何定义和使用混入?
2.1 定义一个混入对象
混入就是一个普通对象,属性名和组件选项一致:data、methods、created、mounted 等。
示例:一个“时间相关”的混入
// mixins/timeMixin.js
export const timeMixin = {
data() {
return {
currentTime: ''
}
},
methods: {
updateTime() {
this.currentTime = new Date().toLocaleTimeString()
}
},
mounted() {
this.updateTime()
this._timeTimer = setInterval(this.updateTime, 1000)
},
beforeUnmount() {
if (this._timeTimer) clearInterval(this._timeTimer)
}
}
- data 必须写成一个函数,返回对象(和组件里 data 的写法一样)。
- mounted、beforeUnmount 等是生命周期钩子。
- 这里用 this._timeTimer 存定时器,在 beforeUnmount 里清除,避免组件销毁后仍执行。
2.2 在组件里使用混入(选项式 API)
在选项式 API 的组件里,通过 mixins 选项把混入引进来:
// 选项式 API 组件
import { timeMixin } from './mixins/timeMixin'
export default {
mixins: [timeMixin],
data() {
return {
name: '我的页面'
}
},
mounted() {
console.log('组件挂载了,当前时间:', this.currentTime)
}
}
合并后,这个组件会拥有:
- data:
currentTime(来自混入)+name(来自组件)。 - methods:
updateTime(来自混入)。 - mounted:先执行混入的 mounted,再执行组件自己的 mounted(详见下文“合并规则”)。
模板里可以直接用混入提供的数据和方法:
<template>
<div>
<p>页面:{{ name }}</p>
<p>当前时间:{{ currentTime }}</p>
</div>
</template>
2.3 混入在 中不可用
没有 mixins 选项,不能写:
// 这样不行
const mixins = [timeMixin]
若你用的是 ,要实现类似“混入”的逻辑复用,请用组合式函数(见第六节)。
三、选项的合并规则(重要)
组件和混入里同名选项会怎样合并?规则如下。
3.1 data、methods、computed、inject、provide
这些选项会合并成一个对象。
若组件和混入有同名的 key,以组件自己的为准(组件覆盖混入)。
const mixin = {
data() {
return { count: 0, name: 'Mixin' }
},
methods: {
fn() {
console.log('mixin fn')
}
}
}
export default {
mixins: [mixin],
data() {
return { name: 'Component', age: 18 }
},
methods: {
fn() {
console.log('component fn')
}
}
}
合并后:
- data:
{ count: 0, name: 'Component', age: 18 }(name 被组件覆盖,count 来自混入,age 来自组件)。 - methods.fn:组件的 fn(组件覆盖混入)。
3.2 生命周期钩子(created、mounted 等)
同名的生命周期钩子会合并成数组,都会执行。
执行顺序:先执行混入的钩子,再执行组件自己的钩子。
const mixin = {
mounted() {
console.log(1)
}
}
export default {
mixins: [mixin],
mounted() {
console.log(2)
}
}
控制台输出顺序:1 → 2。
若有多个混入,混入的钩子按 mixins 数组顺序执行,然后再执行组件的钩子。
3.3 watch
watch 里同名的监听会合并成数组,都会生效;若监听同一属性,会按合并顺序依次执行。
3.4 小结表
| 选项类型 | 合并方式 | 冲突时(组件 vs 混入) |
|---|---|---|
| data、methods 等 | 合并为对象 | 以组件为准 |
| 生命周期钩子 | 合并为数组,都执行 | 先混入,后组件 |
| watch、components 等 | 合并为对象/数组 | 以组件为准(或都执行) |
四、多个混入与全局混入
4.1 使用多个混入
mixins 是一个数组,可以写多个混入;合并时按数组顺序依次合并,后面的不会覆盖前面的同名 data/methods(组件最后合并,组件优先)。
import { timeMixin } from './mixins/timeMixin'
import { logMixin } from './mixins/logMixin'
export default {
mixins: [timeMixin, logMixin],
data() {
return {}
}
}
4.2 全局混入
在 main.js 里用 app.mixin(混入对象),会对当前应用下所有组件生效,慎用。
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mixin({
mounted() {
console.log('每个组件挂载时都会打印')
}
})
app.mount('#app')
- 适合做“全局打点、全局埋点”等,但要避免在全局混入里写大量逻辑或容易重名的 data/methods,否则难以维护、难以排查问题。
- 一般更推荐在需要的地方局部混入,或改用组合式函数。
五、完整示例与常见场景
5.1 混入:请求用户信息
// mixins/userMixin.js
export const userMixin = {
data() {
return {
user: null,
userLoading: false
}
},
methods: {
async fetchUser() {
this.userLoading = true
try {
const res = await fetch('/api/user')
this.user = await res.json()
} finally {
this.userLoading = false
}
}
},
mounted() {
this.fetchUser()
}
}
// 组件
import { userMixin } from './mixins/userMixin'
export default {
mixins: [userMixin],
template: `
<div v-if="userLoading">加载中...</div>
<div v-else-if="user">你好,{{ user.name }}</div>
`
}
5.2 混入:公共方法(格式化、校验)
// mixins/utilsMixin.js
export const utilsMixin = {
methods: {
formatDate(date) {
if (!date) return ''
return new Date(date).toLocaleDateString()
},
isPhone(str) {
return /^1d{10}$/.test(str)
}
}
}
export default {
mixins: [utilsMixin],
methods: {
submit() {
if (!this.isPhone(this.phone)) {
alert('手机号格式错误')
return
}
console.log('提交时间:', this.formatDate(new Date()))
}
}
}
5.3 混入 + 组件都有的 data / 生命周期
const mixin = {
data() {
return { mixinCount: 0 }
},
created() {
console.log('mixin created')
}
}
export default {
mixins: [mixin],
data() {
return { mixinCount: 100, myCount: 1 }
},
created() {
console.log('component created')
}
}
- data:最终
mixinCount为 100(组件覆盖混入),myCount为 1。 - created:先打印
mixin created,再打印component created。
六、混入的局限与替代方案:组合式函数
6.1 混入的常见问题
- 来源不清晰:模板里用的
currentTime、updateTime是组件写的还是混入写的,不打开 mixins 列表很难一眼看出。 - 命名冲突:多个混入或混入与组件有同名 data/methods 时,虽然“以组件为准”,但容易误以为用的是混入的值,导致难以排查的 bug。
- 无法在 中使用:Vue 3 推荐 ,而混入只适用于选项式 API,在新写法里用不上。
因此,Vue 3 官方更推荐用组合式函数(Composables)来做逻辑复用。
6.2 用组合式函数替代混入(推荐)
把“混入”里想复用的 data + methods + 生命周期,改成一个普通函数,在函数里用 ref、reactive、onMounted 等组合式 API,然后 return 出需要给模板或其它逻辑用的数据和方法。
组件里只要 import 并调用这个函数,就能得到同样的能力,且来源清晰、无合并冲突。
原来用混入的“时间”示例,改成组合式函数:
// composables/useTime.js
import { ref, onMounted, onBeforeUnmount } from 'vue'
export function useTime() {
const currentTime = ref('')
let timer = null
function updateTime() {
currentTime.value = new Date().toLocaleTimeString()
}
onMounted(() => {
updateTime()
timer = setInterval(updateTime, 1000)
})
onBeforeUnmount(() => {
if (timer) clearInterval(timer)
})
return { currentTime, updateTime }
}
在 里使用:
<template>
<p>当前时间:{{ currentTime }}</p>
</template>
<script setup>
import { useTime } from './composables/useTime'
const { currentTime } = useTime()
</script>
- 数据和方法来自 useTime(),一眼能看出来源。
- 多个组件用 useTime,各自有一份 currentTime,不会互相串。
- 支持 ,也符合 Vue 3 推荐写法。
“用户信息”示例改成组合式函数:
// composables/useUser.js
import { ref, onMounted } from 'vue'
export function useUser() {
const user = ref(null)
const userLoading = ref(false)
async function fetchUser() {
userLoading.value = true
try {
const res = await fetch('/api/user')
user.value = await res.json()
} finally {
userLoading.value = false
}
}
onMounted(() => {
fetchUser()
})
return { user, userLoading, fetchUser }
}
<script setup>
import { useUser } from './composables/useUser'
const { user, userLoading } = useUser()
</script>
七、何时还用混入?
- 你正在维护选项式 API 的老项目,且已经大量使用 mixins,可以继续按原有方式使用混入,并了解合并规则便于排错。
- 新写 Vue 3 项目或新功能,更推荐用 + 组合式函数,而不是混入。
八、混入速查表
| 内容 | 说明 |
|---|---|
| 定义混入 | 普通对象,包含 data、methods、mounted 等选项;data 必须是函数 |
| 组件中使用 | 选项式 API:mixins: [myMixin] |
| 不能使用 mixins,改用组合式函数 | |
| data/methods 冲突 | 以组件为准 |
| 生命周期冲突 | 都执行,先混入后组件 |
| 全局混入 | app.mixin(对象),慎用 |
| 推荐替代 | 组合式函数(composables) |
九、学习建议
- 若你写的是 ,直接学组合式函数即可,不必在混入上花太多时间。
- 若需要读/改选项式 API + mixins 的代码,重点理解:data/methods 以组件为准、生命周期会合并成数组且先混入后组件。
- 新逻辑复用优先用 useXxx() 这种组合式函数,更清晰、更易维护。
把本文档里的混入示例和对应的组合式函数示例对比着看,能更好理解“为什么 Vue 3 推荐用组合式函数替代混入”。祝你学习顺利。