iT邦幫忙

2025 iThome 鐵人賽

DAY 15
1
Vue.js

Vue3.6 的革新:深入理解 Composition API系列 第 15

Day 15: Composable,Vue 3.6 的「組裝革命螺絲」

  • 分享至 

  • xImage
  •  

這一系列的第一個重頭戲來啦!

Vue 3.6 把底層訊號系統換成 Alien Signals,效能和記憶體占用大幅下降,這意味著:我們在寫 composable 時,不需要任何修改,就能無痛吃到新引擎紅利。

今天,就讓我們從最小的「組裝單位」開始,打開 Composition API 的下一扇門。

為什麼需要 Composable


當邏輯開始重複(例如:計數器、API 請求、事件監聽),與其複製貼上或寫成大型 Store,不如抽成一個函式:

  • 跨元件共用:一次封裝,多處呼叫
  • 生命週期感知:跟著呼叫的元件/作用域自動清理
  • 組合式:composable 之間還能互相拼裝

一句話:composable 是 Vue 3.6 世界裡,最輕量、最靈活的「邏輯積木」。

官方文件最常以計數器當範例,今天也用一樣的範例實作。

程式碼觀察每次呼叫 useCounter() 就得到一份獨立狀態,沒有任何全域污染。
因為在 Vue 的世界應用原理有點像 JavaScript 的閉包,內部方法記住了 composable 內部的響應式狀態。

import { ref, computed } from 'vue'

// 變數都在 fn 裡面,所以不會跨呼叫共用記憶體
export function useCounter(initial = 0) {
  const count = ref(initial)  // 每次呼叫才執行,產生新的 ref 與 computed 實例
  const double = computed(() => count.value * 2)
  const inc = (n = 1) => (count.value += n)

  return { count, double, inc }
}

呼叫方式:

<script setup lang="ts">
import { useCounter } from './useCounter'
const { count, double, inc } = useCounter(10)
</script>

<template>
  <button @click="inc()">+1</button>
  <p>{{ count }} / {{ double }}</p>
</template>

使用限制

  1. <script setup> 底下註冊使用
  2. watch 下不該使用 composable => 在 watch 下使用會容易造成記憶體洩漏的問題,如果需要用到一樣先在 <script setup> 下註冊後再把取得的資料傳遞進去給 watch 使用喔!
  3. 生命週期中不要用 composable => 因為可以在 composable 中放生命週期,元件中本來就有一個生命週期,就無法判斷哪一個會先執行或是根本不會被執行到!?
    composable 使用限制

副作用與清理:onScopeDispose


Vue 3.6 引入 Effect Scope 強化,推薦在 composable 裡用 onScopeDispose 管理資源,不論在元件或測試的 scope 內,都能自動釋放。

  • 作用域類型
    1. 元件作用域 → 等價於 onUnmounted
    2. 自建 effectScope → 不一定要等到整個元件銷毀,scope.stop() 時就會觸發
  • 使用場景
    • composable 中註冊清理邏輯 → 會自動跟隨呼叫它的作用域走。
    • effectScope 測試或臨時使用 → 可以提早手動 stop() 釋放。
import { ref, onMounted, onScopeDispose } from 'vue'

export function useMouse() {
  const x = ref(0), y = ref(0)
  const move = (e: MouseEvent) => { 
    x.value = e.pageX
    y.value = e.pageY 
  }

  onMounted(() => window.addEventListener('mousemove', move))
  onScopeDispose(() => window.removeEventListener('mousemove', move))

  return { x, y }
}

onScopeDispose 是 Composable 的保險絲:
元件卸載、scope.stop()、甚至 server-side 渲染都能安全清理。

onScopeDisposeonUnmounted 對於「清理資源」的差異


一般來說,如果沒有使用 vueuse 套件就會自己寫一個 composable 然後在裡面註冊了外部資源(window.addEventListener)進入後取得滑鼠座標,做些動畫處理,但是需要做清理資源,以下兩種寫法各有不同:

onUnmounted:只能在「元件」作用域運作 → 這樣 useMouse 就只侷限於元件使用。

import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0), y = ref(0)
  const move = (e: MouseEvent) => { x.value = e.pageX; y.value = e.pageY }
  onMounted(() => window.addEventListener('mousemove', move))
  onUnmounted(() => window.removeEventListener('mousemove', move))

  return { x, y }
}

onScopeDispose:會跟隨呼叫它的 最近作用域(元件 or effectScope)

import { ref, onMounted, onScopeDispose } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  const move = (e: MouseEvent) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', move))
  onScopeDispose(() => window.removeEventListener('mousemove', move))

  return { x, y }
}

以下是一個 codepen 的實戰範例,透過程式碼可以了解 Vue 清理機制:

Vue 3 useMouse composable
https://codepen.io/MengTing-Ku-the-typescripter/pen/raOKzEv

Vue 清理機制:作用域對照圖

實戰心法


  1. 回傳 ref/ computed,少回傳 reactive:避免解構失去響應性。
  2. 參數化:提供初始值、options,提升可重用度。
  3. 組合再組合useFetch 裡可以套 useDebounceuseEventListener
  4. 共享狀態? 真正要跨頁面共享,用 Pinia;composable 偏向「可複製的私有狀態」。

小結


Composable 不是 Vue 3.6 的新 API,但因 Alien Signals 的高效訊號系統,它成為享受新引擎紅利的第一現場:邏輯越抽離 → 受益越明顯。

下一篇,我們將把多個 composable 拼裝起來,看看 Vue 3.6 如何在「組合」這件事上,跑得更快。

參考資料


  1. Vue.js - composables
  2. VueUse 中文網

上一篇
Day 14: 小專案實作:整合已學知識,製作簡單應用
系列文
Vue3.6 的革新:深入理解 Composition API15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言