從 Vue 3 開始:組合式函數(Composables)實戰指南

如果你是前端工程師,用 Vue 3 寫過幾個專案後,一定會遇到同一個困擾:同樣的邏輯在 A 元件寫一遍、在 B 元件又寫一遍,複製貼上成了日常,程式碼越來越難維護。今天我們要聊的主角——Composables(組合式函數),正是為了解決這個問題而生。

一、為什麼需要 Composables?

先回想一下 Vue 2 時代的兩個「老朋友」:

  • Mixins:可以複用邏輯,但來源不明、命名衝突、隱式依賴,debug 起來像在「找幽靈」
  • Renderless Components:用 slot 傳資料,但元件巢狀越疊越深,可讀性直線下降

Vue 3 的 Composition API 給出了更優雅的答案:把邏輯抽成一個普通的 JavaScript 函數,需要時在任何元件裡「呼叫」即可。就像樂高積木,函式是單塊積木,Composable 就是把積木拼成可直接使用的「模組」

二、先認識 setup 與組合式函數

Composition API 的核心是 setup:它比 data/computed/methods 更靈活,讓你可以把相關的狀態與方法「聚集」在一起。

重點:Composable 只是「約定俗成」的寫法——一個以 use 開頭、接收參數、回傳物件或變數的函數,沒有限制你必須怎麼寫,所以極度輕量。

// useCounter.js —— 第一個 Composable
import { ref, computed } from 'vue'

export function useCounter(initial = 0) {
  const count = ref(initial)
  const double = computed(() => count.value * 2)

  function increment() {
    count.value++
  }

  return { count, double, increment }
}

在元件中使用時,只需要一行呼叫:

import { useCounter } from './useCounter'

const { count, double, increment } = useCounter(10)

與原本寫在元件裡的 setup 相比,邏輯被搬到了獨立檔案中,多個元件可以共享同一份狀態邏輯,而且每個呼叫都是「獨立副本」,互不干擾。

三、實戰:抽取一個 useFetch

前端的日常少不了請求 API。我們來把「請求 + 載入狀態 + 錯誤處理」抽成一個可複用的 useFetch

步驟 1:建立 useFetch.js

import { ref, onMounted } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(true)
  const error = ref(null)

  async function fetchData() {
    loading.value = true
    error.value = null
    try {
      const res = await fetch(url)
      if (!res.ok) throw new Error('HTTP ' + res.status)
      data.value = await res.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }

  onMounted(fetchData) // 元件掛載時自動請求

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

步驟 2:在任何元件中使用

import { useFetch } from '../composables/useFetch'

const { data, loading, error, refetch } = useFetch(
  'https://api.example.com/posts'
)
<template>
  <p v-if="loading">載入中…</p>
  <p v-else-if="error">出錯了:{{ error.message }}</p>
  <ul v-else>
    <li v-for="item in data" :key="item.id">{{ item.title }}</li>
  </ul>
  <button @click="refetch">重新載入</button>
</template>

特別注意:如果多個元件都呼叫了 useFetch,每個元件會拿到自己獨立的 data/loading/error。若你想要「全域共享一份資料」,這就要交給狀態管理工具了。

四、進階:Composable 與 Pinia 的分工

很多新手會問:有了 Composable,還需要 Pinia 嗎?答案是兩者定位不同:

  • Composable 適合:元件內部的邏輯複用,例如計數器、表單驗證、請求封裝、定時器、事件監聽
  • Pinia 適合:跨元件的「全域狀態」,例如使用者資訊、購物車、權限列表——需要被多個頁面共享且持久化

實務上的最佳做法是:用 Composable 封裝「行為邏輯」,用 Pinia 管理「共享狀態」,兩者還能互相配合,例如在 Store 內部呼叫 Composable 來簡化程式碼。

五、總結

  • Composable 是 Vue 3 邏輯複用的「正解」,取代了 Mixins 的種種缺點
  • use 開頭命名,接受參數、回傳狀態與方法,輕量且靈活
  • 每個呼叫都是獨立實例,適合元件內部邏輯;跨頁面共享請交給 Pinia
  • 實戰中建議:先從「重複第三次」的程式碼開始抽取,避免過度抽象

你在專案中習慣用 Composable 處理哪些場景?有沒有遇過「不知道該放 Composable 還是 Pinia」的糾結?歡迎在留言區分享你的實戰經驗,我們一起討論!