跳至內容

Vue 3 Composition API 實戰模式與最佳實踐

Vue 3 的 Composition API 提供了更靈活的程式碼組織方式,但實際用起來卻讓不少人感到困惑。這篇文章想跟你分享我這幾年使用 Composition API 的實戰經驗和常用模式。


為什麼選擇 Composition API?

在開始之前,先來聊聊 Composition API 到底解決了什麼問題:

  1. 邏輯複用性:Options API 在複用邏輯時常需要使用 mixins,容易產生命名衝突和來源不明的問題
  2. 程式碼組織:Options API 將相關邏輯分散在不同的選項中(data、methods、computed),不利於閱讀和維護
  3. 型別推導:Composition API 對 TypeScript 的支援更加友善

實戰模式一:Composable 函式的設計原則

單一職責原則

一個好的 composable 應該只專注於一件事。以使用者認證為例:

javascript
// ❌ 不好的做法:職責過多
function useAuth() {
  const user = ref(null)
  const isLoggedIn = ref(false)
  const notifications = ref([])
  const settings = ref({})

  // 混雜了認證、通知、設定等多個功能
}

// ✅ 好的做法:拆分成多個 composable
function useAuth() {
  const user = ref(null)
  const isLoggedIn = computed(() => !!user.value)

  const login = async (credentials) => {
    // 登入邏輯
  }

  const logout = () => {
    user.value = null
  }

  return { user, isLoggedIn, login, logout }
}

function useNotifications() {
  // 專注於通知功能
}

function useUserSettings() {
  // 專注於使用者設定
}

命名規範

Composable 函式應該:

  • use 開頭(遵循 Vue 社群慣例)
  • 使用動詞或名詞描述功能
  • 清楚表達其職責
javascript
// 推薦的命名
useMousePosition()
useFetch()
useLocalStorage()
useFormValidation()

實戰模式二:狀態管理的分層架構

在大型專案中,我建議採用分層的狀態管理策略:

1. 元件級狀態(Component State)

僅在單一元件內使用的狀態,直接使用 refreactive

vue
<script setup>
import { ref } from 'vue'

const isOpen = ref(false)
const toggleMenu = () => {
  isOpen.value = !isOpen.value
}
</script>

2. 共享狀態(Shared State)

跨多個元件共享的狀態,使用 composable 封裝:

javascript
// composables/useCounter.js
import { ref } from 'vue'

// 在模組作用域中定義,所有使用此 composable 的元件共享同一個狀態
const count = ref(0)

export function useCounter() {
  const increment = () => {
    count.value++
  }

  const decrement = () => {
    count.value--
  }

  return { count: readonly(count), increment, decrement }
}

3. 全域狀態(Global State)

複雜的應用級狀態,考慮使用 Pinia:

javascript
// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  const permissions = computed(() => user.value?.permissions || [])

  const fetchUser = async () => {
    // API 呼叫
  }

  return { user, permissions, fetchUser }
})

實戰模式三:副作用的處理

資料獲取(Data Fetching)

處理非同步資料獲取時,建議封裝載入狀態和錯誤處理:

javascript
function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  const fetch = async () => {
    loading.value = true
    error.value = null

    try {
      const response = await axios.get(url.value)
      data.value = response.data
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  // 自動執行
  watchEffect(() => {
    fetch()
  })

  return { data, error, loading, refetch: fetch }
}

事件監聽的清理

使用 onUnmounted 確保事件監聽器被正確清理:

javascript
function useEventListener(target, event, handler) {
  onMounted(() => {
    target.addEventListener(event, handler)
  })

  onUnmounted(() => {
    target.removeEventListener(event, handler)
  })
}

實戰模式四:效能最佳化

避免不必要的響應式

不是所有資料都需要是響應式的:

javascript
// ❌ 不必要的響應式
const config = reactive({
  apiUrl: 'https://api.example.com',
  timeout: 5000
})

// ✅ 常數不需要響應式
const CONFIG = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
}

善用 computed 快取

computed 會快取計算結果,只在相依的響應式資料變化時重新計算:

javascript
// ✅ 使用 computed 快取昂貴的計算
const expensiveValue = computed(() => {
  return items.value.reduce((sum, item) => {
    // 複雜計算
    return sum + calculateScore(item)
  }, 0)
})

大型列表的效能優化

對於大型列表,考慮使用 shallowRefshallowReactive

javascript
// 當不需要深層響應式時
const largeList = shallowRef([])

// 更新整個陣列
largeList.value = newList

總結

Composition API 雖然強大,但也需要遵循一些基本原則:

  1. 保持 composable 的單一職責
  2. 根據狀態的作用域選擇合適的管理方式
  3. 妥善處理副作用和生命週期
  4. 關注效能,避免過度使用響應式

這些模式不是什麼絕對真理,而是我在實務開發中踩過坑後的心得。實際用的時候還是要看專案狀況彈性調整。最重要的是讓程式碼保持可讀性和可維護性,這樣團隊成員才能順暢協作。

💬 留言討論

Released under the MIT License.