#Vue.js Mixins (mixed in)
#What are Mixins?
Mixins are a way to distribute reusable functionality in Vue components. A mixin object can contain arbitrary component options. When a component uses a mixin, all options of the mixin will be "mixed" into the options of the component itself.
Note: In Vue 3, it is recommended to use combinable functions (Composables) instead of Mixins. Mixins are mainly used in optional APIs.
#Basic usage (optional API)
<script>
// 定义一个 mixin
const myMixin = {
data() {
return {
message: 'Hello from mixin'
}
},
created() {
console.log('Mixin created hook called')
},
methods: {
greet() {
console.log(this.message)
}
}
}
export default {
mixins: [myMixin],
data() {
return {
localMessage: 'Hello from component'
}
},
created() {
console.log('Component created hook called')
this.greet()
}
}
</script>
<template>
<div>
<p>{{ message }}</p>
<p>{{ localMessage }}</p>
</div>
</template>##Option merge
When a component and a mixin have options with the same name, they are merged:
#1. Data object merging
const mixin = {
data() {
return {
message: 'mixin message',
foo: 'mixin foo'
}
}
}
export default {
mixins: [mixin],
data() {
return {
message: 'component message', // 组件的数据优先
bar: 'component bar'
}
}
}
// 结果:
// {
// message: 'component message', // 组件优先
// foo: 'mixin foo',
// bar: 'component bar'
// }#2. Life cycle hook merge
const mixin = {
created() {
console.log('mixin created')
}
}
export default {
mixins: [mixin],
created() {
console.log('component created')
}
}
// 输出:
// mixin created
// component created
// (mixin 的钩子先调用)#3. Merge methods, components, etc.
const mixin = {
methods: {
foo() {
console.log('mixin foo')
},
bar() {
console.log('mixin bar')
}
}
}
export default {
mixins: [mixin],
methods: {
bar() {
console.log('component bar') // 组件的方法优先
}
}
}#Practical example: Log Mixin
<script>
// loggerMixin.js
export const loggerMixin = {
data() {
return {
logHistory: []
}
},
methods: {
log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString()
const logEntry = {
message,
type,
timestamp
}
this.logHistory.push(logEntry)
console.log(`[${type.toUpperCase()}] ${timestamp}: ${message}`)
},
clearLogs() {
this.logHistory = []
}
}
}
// 使用 mixin
export default {
mixins: [loggerMixin],
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
this.log(`Count incremented to ${this.count}`)
},
decrement() {
this.count--
this.log(`Count decremented to ${this.count}`)
}
},
mounted() {
this.log('Component mounted', 'success')
}
}
</script>
<template>
<div class="logger-demo">
<h3>计数器:{{ count }}</h3>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="clearLogs">清空日志</button>
<div class="logs">
<h4>日志记录</h4>
<div v-for="(log, index) in logHistory" :key="index" :class="['log-entry', log.type]">
<span class="timestamp">{{ log.timestamp }}</span>
<span class="message">{{ log.message }}</span>
</div>
</div>
</div>
</template>
<style scoped>
.logger-demo {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
button {
margin: 5px;
padding: 8px 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.logs {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 8px;
max-height: 300px;
overflow-y: auto;
}
.log-entry {
padding: 8px;
margin-bottom: 5px;
border-left: 3px solid #42b983;
background-color: white;
border-radius: 4px;
}
.log-entry.success {
border-left-color: #67c23a;
}
.log-entry.error {
border-left-color: #f56c6c;
}
.timestamp {
color: #999;
font-size: 12px;
margin-right: 10px;
}
</style>#Vue 3 Recommendation: Composables
In Vue 3, it is recommended to use combined functions instead of Mixins:
#Mixin method (not recommended)
// mixin
const counterMixin = {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++
}
}
}#Combined function method (recommended)
<script setup>
import { ref } from 'vue'
// 组合式函数
function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = initialValue
}
return {
count,
increment,
decrement,
reset
}
}
// 使用组合式函数
const { count, increment, decrement, reset } = useCounter(0)
</script>
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
<button @click="reset">重置</button>
</div>
</template>#Practical example: form validation combined function
<script setup>
import { ref, computed } from 'vue'
// 表单验证组合式函数
function useFormValidation() {
const errors = ref({})
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!email) {
return '邮箱不能为空'
} else if (!emailRegex.test(email)) {
return '邮箱格式不正确'
}
return ''
}
function validatePassword(password) {
if (!password) {
return '密码不能为空'
} else if (password.length < 6) {
return '密码至少6个字符'
}
return ''
}
function validateRequired(value, fieldName) {
if (!value) {
return `${fieldName}不能为空`
}
return ''
}
function setError(field, message) {
if (message) {
errors.value[field] = message
} else {
delete errors.value[field]
}
}
function clearErrors() {
errors.value = {}
}
const hasErrors = computed(() => {
return Object.keys(errors.value).length > 0
})
return {
errors,
validateEmail,
validatePassword,
validateRequired,
setError,
clearErrors,
hasErrors
}
}
// 使用组合式函数
const form = ref({
email: '',
password: '',
username: ''
})
const {
errors,
validateEmail,
validatePassword,
validateRequired,
setError,
clearErrors,
hasErrors
} = useFormValidation()
function handleEmailBlur() {
const error = validateEmail(form.value.email)
setError('email', error)
}
function handlePasswordBlur() {
const error = validatePassword(form.value.password)
setError('password', error)
}
function handleUsernameBlur() {
const error = validateRequired(form.value.username, '用户名')
setError('username', error)
}
function handleSubmit() {
clearErrors()
setError('email', validateEmail(form.value.email))
setError('password', validatePassword(form.value.password))
setError('username', validateRequired(form.value.username, '用户名'))
if (!hasErrors.value) {
alert('表单验证通过!')
console.log('提交数据:', form.value)
}
}
</script>
<template>
<form @submit.prevent="handleSubmit" class="validation-form">
<h2>注册表单</h2>
<div class="form-group">
<label>用户名</label>
<input
v-model="form.username"
@blur="handleUsernameBlur"
placeholder="请输入用户名"
/>
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>
<div class="form-group">
<label>邮箱</label>
<input
v-model="form.email"
@blur="handleEmailBlur"
type="email"
placeholder="请输入邮箱"
/>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<div class="form-group">
<label>密码</label>
<input
v-model="form.password"
@blur="handlePasswordBlur"
type="password"
placeholder="请输入密码"
/>
<span v-if="errors.password" class="error">{{ errors.password }}</span>
</div>
<button type="submit">提交</button>
</form>
</template>
<style scoped>
.validation-form {
max-width: 400px;
margin: 20px auto;
padding: 30px;
border: 1px solid #ddd;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.error {
display: block;
color: #f56c6c;
font-size: 12px;
margin-top: 5px;
}
button {
width: 100%;
padding: 12px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #35a372;
}
</style>#Practical example: mouse position tracking
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 鼠标位置追踪组合式函数
function useMousePosition() {
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 }
}
// 使用组合式函数
const { x, y } = useMousePosition()
</script>
<template>
<div class="mouse-tracker">
<h2>鼠标位置追踪</h2>
<div class="position-display">
<p>X: {{ x }}px</p>
<p>Y: {{ y }}px</p>
</div>
<div
class="follower"
:style="{ left: x + 'px', top: y + 'px' }"
></div>
</div>
</template>
<style scoped>
.mouse-tracker {
padding: 20px;
min-height: 400px;
position: relative;
}
.position-display {
position: fixed;
top: 20px;
right: 20px;
padding: 15px;
background-color: rgba(66, 185, 131, 0.9);
color: white;
border-radius: 8px;
font-family: monospace;
z-index: 1000;
}
.follower {
position: fixed;
width: 20px;
height: 20px;
background-color: #42b983;
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
transition: all 0.1s;
z-index: 999;
}
</style>#Mixins vs combined functions comparison
| Features | Mixins | Combined functions |
|---|---|---|
| Naming conflicts | May occur | Explicit return values |
| Source Tracing | Difficulty | Clarity |
| Type Inference | Poor | Excellent |
| Code reuse | Limited | Flexible |
| Combination abilities | Limited | Powerful |
| Vue 3 Recommended | ❌ | ✅ |
#Questions about Mixins
#1. Naming conflict
// mixin1
const mixin1 = {
data() {
return { count: 0 }
}
}
// mixin2
const mixin2 = {
data() {
return { count: 10 } // 冲突!
}
}
// 组件
export default {
mixins: [mixin1, mixin2] // count 的值不确定
}#2. Implicit dependencies
const mixin = {
methods: {
doSomething() {
// 依赖组件的 someData,但不明显
console.log(this.someData)
}
}
}#3. Difficult to trace the source
export default {
mixins: [mixin1, mixin2, mixin3],
// 这个方法来自哪个 mixin?
mounted() {
this.someMethod() // 来源不明确
}
}#Summarize
- Mixins is a way to reuse code in Vue 2, and is not recommended in Vue 3
- Combined functions (Composables) are the code reuse method recommended by Vue 3
- Combined functions are more flexible, clearer and easier to maintain
- Combined functions avoid naming conflicts and implicit dependency problems
- Use composed functions for better type inference and IDE support
#Next step
Next, learn State Management to understand the use of Vuex and Pinia.