Vue 3 Composition API 實戰模式與最佳實踐
Vue 3 的 Composition API 提供了更靈活的程式碼組織方式,但實際用起來卻讓不少人感到困惑。這篇文章想跟你分享我這幾年使用 Composition API 的實戰經驗和常用模式。
為什麼選擇 Composition API?
在開始之前,先來聊聊 Composition API 到底解決了什麼問題:
- 邏輯複用性:Options API 在複用邏輯時常需要使用 mixins,容易產生命名衝突和來源不明的問題
- 程式碼組織:Options API 將相關邏輯分散在不同的選項中(data、methods、computed),不利於閱讀和維護
- 型別推導: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)
僅在單一元件內使用的狀態,直接使用 ref 或 reactive:
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)
})大型列表的效能優化
對於大型列表,考慮使用 shallowRef 或 shallowReactive:
javascript
// 當不需要深層響應式時
const largeList = shallowRef([])
// 更新整個陣列
largeList.value = newList總結
Composition API 雖然強大,但也需要遵循一些基本原則:
- 保持 composable 的單一職責
- 根據狀態的作用域選擇合適的管理方式
- 妥善處理副作用和生命週期
- 關注效能,避免過度使用響應式
這些模式不是什麼絕對真理,而是我在實務開發中踩過坑後的心得。實際用的時候還是要看專案狀況彈性調整。最重要的是讓程式碼保持可讀性和可維護性,這樣團隊成員才能順暢協作。