這一系列的第一個重頭戲來啦!
Vue 3.6 把底層訊號系統換成 Alien Signals,效能和記憶體占用大幅下降,這意味著:我們在寫 composable 時,不需要任何修改,就能無痛吃到新引擎紅利。
今天,就讓我們從最小的「組裝單位」開始,打開 Composition API 的下一扇門。
當邏輯開始重複(例如:計數器、API 請求、事件監聽),與其複製貼上或寫成大型 Store,不如抽成一個函式:
一句話: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>
使用限制
- 在
<script setup>
底下註冊使用watch
下不該使用 composable => 在watch
下使用會容易造成記憶體洩漏的問題,如果需要用到一樣先在<script setup>
下註冊後再把取得的資料傳遞進去給watch
使用喔!- 生命週期中不要用 composable => 因為可以在 composable 中放生命週期,元件中本來就有一個生命週期,就無法判斷哪一個會先執行或是根本不會被執行到!?
![]()
onScopeDispose
Vue 3.6 引入 Effect Scope 強化,推薦在 composable 裡用 onScopeDispose
管理資源,不論在元件或測試的 scope 內,都能自動釋放。
onUnmounted
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 渲染都能安全清理。
onScopeDispose
跟 onUnmounted
對於「清理資源」的差異一般來說,如果沒有使用 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
ref
/ computed
,少回傳 reactive
:避免解構失去響應性。useFetch
裡可以套 useDebounce
、useEventListener
。Pinia
;composable 偏向「可複製的私有狀態」。Composable 不是 Vue 3.6 的新 API,但因 Alien Signals 的高效訊號系統,它成為享受新引擎紅利的第一現場:邏輯越抽離 → 受益越明顯。
下一篇,我們將把多個 composable 拼裝起來,看看 Vue 3.6 如何在「組合」這件事上,跑得更快。