Vue 中 mixins 的使用方法

在 Vue 開發中,當多個組件需要共享相同的邏輯(比如數據、方法、生命週期鈎子)時,重複編寫這些代碼會造成冗餘。mixins 就像一個 “邏輯共享容器”,能把這些通用邏輯提取出來,讓多個組件直接 “混入” 使用,既減少代碼重複,又方便統一維護。

最基礎的用法是創建一個通用 mixin,包含共享的數據和方法,然後在多個組件中引入。比如多個組件都需要處理倒計時邏輯,就可以把倒計時相關代碼提取到 mixin 中:

<!-- 定義mixin:countdown.mixin.js -->
export default {
  data() {
    return {
      countdown: 60,
      timer: null
    }
  },
  methods: {
    startCountdown() {
      // 清除已有定時器,避免重複
      if (this.timer) clearInterval(this.timer)
      this.countdown = 60
      this.timer = setInterval(() => {
        if (this.countdown > 1) {
          this.countdown--
        } else {
          clearInterval(this.timer)
          this.timer = null
        }
      }, 1000)
    }
  },
  beforeUnmount() {
    // 組件卸載時清除定時器
    if (this.timer) clearInterval(this.timer)
  }
}

<!-- 組件A:使用倒計時mixin -->
<template>
  <div class="component-a">
    <h3>組件A</h3>
    <button @click="startCountdown" :disabled="timer">
      {{ timer ? `倒計時${countdown}秒` : '重新發送' }}
    </button>
  </div>
</template>

<script setup>
import { defineComponent } from 'vue'
import countdownMixin from './countdown.mixin.js'

// 引入mixin
export default defineComponent({
  mixins: [countdownMixin]
})
</script>

<!-- 組件B:同樣使用倒計時mixin -->
<template>
  <div class="component-b">
    <h3>組件B</h3>
    <button @click="startCountdown" :disabled="timer">
      {{ timer ? `倒計時${countdown}秒` : '獲取驗證碼' }}
    </button>
  </div>
</template>

<script setup>
import { defineComponent } from 'vue'
import countdownMixin from './countdown.mixin.js'

export default defineComponent({
  mixins: [countdownMixin]
})
</script>

這裏 countdown.mixin.js 包含了倒計時所需的數據(countdown、timer)、方法(startCountdown)和生命週期鈎子(beforeUnmount),組件 A 和組件 B 引入後,無需重複編寫代碼,就能直接使用這些數據和方法,實現倒計時功能。

mixins 不僅能共享方法和數據,還能共享生命週期鈎子。比如多個組件都需要在掛載時發送初始化請求,就可以把請求邏輯提取到 mixin 中:

<!-- 定義mixin:fetch.mixin.js -->
export default {
  data() {
    return {
      loading: false,
      dataList: []
    }
  },
  mounted() {
    // 組件掛載時自動發送請求
    this.fetchData()
  },
  methods: {
    async fetchData() {
      this.loading = true
      try {
        // 實際開發中替換為真實接口
        const res = await fetch(this.apiUrl)
        this.dataList = await res.json()
      } catch (err) {
        console.error('請求失敗:', err)
      } finally {
        this.loading = false
      }
    }
  }
}

<!-- 商品列表組件 -->
<template>
  <div class="product-list">
    <h3>商品列表</h3>
    <div v-if="loading">加載中...</div>
    <ul v-else>
      <li v-for="item in dataList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script setup>
import { defineComponent } from 'vue'
import fetchMixin from './fetch.mixin.js'

export default defineComponent({
  mixins: [fetchMixin],
  data() {
    return {
      // 覆蓋mixin中的apiUrl,指定當前組件的接口地址
      apiUrl: '/api/products'
    }
  }
})
</script>

<!-- 用户列表組件 -->
<template>
  <div class="user-list">
    <h3>用户列表</h3>
    <div v-if="loading">加載中...</div>
    <ul v-else>
      <li v-for="item in dataList" :key="item.id">{{ item.username }}</li>
    </ul>
  </div>
</template>

<script setup>
import { defineComponent } from 'vue'
import fetchMixin from './fetch.mixin.js'

export default defineComponent({
  mixins: [fetchMixin],
  data() {
    return {
      apiUrl: '/api/users' // 不同組件指定不同接口
    }
  }
})
</script>

這個 fetch.mixin.js 包含了加載狀態、數據列表和請求方法,組件引入後只需指定自己的 apiUrl,就能自動在掛載時發送請求並渲染數據。如果組件需要修改 mixin 中的邏輯,還可以直接覆蓋(比如重寫 fetchData 方法),靈活性很高。

在 Vue 3 的組合式 API 中,mixins 可以結合 setup 語法使用,通過導入 mixin 並執行的方式複用邏輯(更推薦用組合式函數,但 mixins 仍兼容):

<!-- 組合式API風格的mixin:theme.mixin.js -->
export default function themeMixin() {
  const theme = ref('light')
  
  const toggleTheme = () => {
    theme.value = theme.value === 'light' ? 'dark' : 'light'
    // 存儲主題到本地存儲
    localStorage.setItem('theme', theme.value)
  }
  
  // 初始化時讀取本地存儲的主題
  onMounted(() => {
    const savedTheme = localStorage.getItem('theme')
    if (savedTheme) theme.value = savedTheme
  })
  
  return {
    theme,
    toggleTheme
  }
}

<!-- 組件中使用 -->
<template>
  <div :class="theme">
    <h3>組合式API + mixins</h3>
    <button @click="toggleTheme">切換{{ theme === 'light' ? '深色' : '淺色' }}主題</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import themeMixin from './theme.mixin.js'

// 執行mixin,獲取共享的狀態和方法
const { theme, toggleTheme } = themeMixin()
</script>

<style>
.light {
  background: white;
  color: #333;
}
.dark {
  background: #333;
  color: white;
}
</style>

需要注意 mixins 的合併規則:

  1. 數據對象(data):組件的數據會覆蓋 mixin 中的數據(同名時以組件為準);

  2. 方法(methods)、計算屬性(computed):同名時組件的實現會覆蓋 mixin;

  3. 生命週期鈎子:組件和 mixin 中的同名鈎子都會執行,mixin 的鈎子先執行,組件的後執行。

比如 mixin 和組件都有 created 鈎子:

// mixin
created() {
  console.log('mixin的created')
}

// 組件
created() {
  console.log('組件的created')
}

// 執行順序:先打印“mixin的created”,再打印“組件的created”

mixins 的優勢在於簡單直接,適合提取通用邏輯,但也有侷限性:當多個 mixins 存在同名屬性或方法時,優先級難以管理;mixins 和組件之間的依賴關係不明確,調試時可能需要追溯多個 mixins。因此在 Vue 3 中,更推薦使用組合式函數(Composables)替代複雜場景的 mixins,但對於簡單的邏輯共享,mixins 依然是高效的選擇。

總的來説,mixins 是 Vue 中實現邏輯複用的經典方式,尤其適合多個組件共享相似數據、方法或生命週期邏輯的場景。合理使用 mixins 能大幅減少代碼冗餘,讓代碼維護更高效,但需注意避免過度使用和命名衝突,確保代碼的可讀性和可維護性。