iT邦幫忙

2025 iThome 鐵人賽

DAY 18
0
Vue.js

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

Day 18: Effect Scope 與 onScopeDispose - Vue 3.6 的作用域管理術

  • 分享至 

  • xImage
  •  

我們學了如何打造「單一職責、可組合」的 composable,並且讓多個 composable 一起運作時,充分發揮 Vue 3.6 的效能優勢。

但如果我們拆出了許多小 composable、又同時組裝在一起,就會遇到一個問題:誰來負責清理這些副作用?

今天我們深入 Composition API 的核心:作用域管理 (Scope Management),看看 Vue 3.6 如何在 runtime 層面幫我們做得更快、更乾淨。

作用域是什麼?


在 Vue 中,每個元件都是一個響應式作用域 (Reactive Scope):

  • 元件作用域:元件掛載 (mount) → 啟動副作用
  • 元件卸載 (unmount) → 自動停止副作用 (watch / computed / effect)

然而,當我們開始拆分 composable,狀況變得更複雜:

  • Composable 可能被多個元件重複呼叫。
  • 有些副作用只跟「呼叫它的 scope」有關,不一定跟元件生命週期綁死。

Vue 3.6 在 runtime 中,進一步優化了作用域的建立與銷毀流程,讓系統在清理多層巢狀 composable 時更輕量,避免多餘的依賴重算。

onScopeDispose: composable 的專屬清理鈎子


在 composable 裡,我們通常使用 onScopeDispose 來清理副作用。

但是多數上還是使用 onUnmountedonScopeDisposeonUnmounted 有什麼差異呢?

Hook 清理時機 典型用途
onUnmounted 元件卸載時清理 元件內 watchsetInterval
onScopeDispose 呼叫 composable 的 scope 銷毀時清理 composable 內部的副作用

範例:滑鼠座標監聽

import { ref, onScopeDispose } from 'vue'

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

  function update(e: MouseEvent) {
    x.value = e.pageX
    y.value = e.pageY
  }

  window.addEventListener('mousemove', update)

  // 重點:跟呼叫者的 effect scope 綁定
  onScopeDispose(() => {
    window.removeEventListener('mousemove', update)
  })

  return { x, y }
}

如果這個 composable 在一個 <script setup> 內被呼叫,當元件卸載時會自動清理。
如果它被另一個 composable 呼叫,只要呼叫的人 scope 結束,也能正確清理。

進階控制:effectScope


有時候,我們需要建立脫離元件生命週期的「臨時作用域」,動態啟動/停止副作用 (例如:切換 tab、開啟 modal 時才啟動計時器),或是在非元件環境 (例如 Pinia store) 中手動管理資源。

這時可以使用 effectScope 直接創建一個獨立 scope:

import { effectScope, ref, watch } from 'vue'

// 建立一個獨立的 scope
const scope = effectScope()

scope.run(() => {
  const count = ref(0)
  watch(count, () => console.log('changed:', count.value))
  setInterval(() => count.value++, 1000)
})

// 任何時候都可以手動停止
scope.stop() // 停止後,自動清理所有 watch / effect

巢狀 scope: 多 composable 的安全組裝


Vue 3.6 在 runtime 優化了巢狀 scope的建立與銷毀:

  • 當一個元件呼叫多個 composable → Vue 會為每個呼叫自動建立子 scope。
  • 子 scope 被銷毀時,對應的副作用立即清理,不會影響其他 composable。
export function useAutoRefresh(api: () => Promise<any>) {
  const data = ref<any>(null)
  const timer = setInterval(async () => {
    data.value = await api()
  }, 3000)

  // 無論是元件還是其他 composable 清理,每個 onScopeDispose 只會清理自己的範圍
  onScopeDispose(() => clearInterval(timer))

  return { data }
}

實戰案例:頁籤 tab 的動態切換


不管是前台還是後台,設計很常會有頁籤切換內容,以下示範一個「進入 Tab → 啟動輪詢、離開 → 自動清理」的場景:

import { effectScope, ref } from 'vue'
import { useAutoRefresh } from './useAutoRefresh'

export function useTabData() {
  // 作用域內所有建立的 reactive / watch / computed / 自動副作用,都會被 effectScope 記錄
  const current = ref('home')
  let tabScope: ReturnType<typeof effectScope> | null = null

  function enterTab(name: string) {
    current.value = name
    tabScope?.stop() // 清理前一個 tab 整個 scope 裡的副作用會一次清掉(例如定時器、watcher)
    tabScope = effectScope()
    tabScope.run(() => {
      useAutoRefresh(() => fetch(`/api/${name}`))
    })
  }

  function leaveTab() {
    tabScope?.stop()
    tabScope = null
  }

  return { current, enterTab, leaveTab }
}

Vue 3.6 的底層優化亮點


Vue 3.6 在 runtime 中對 effect scope 與依賴追蹤 做了以下優化:

  • 細粒度清理:Alien Signals 只通知真正受影響的副作用。
  • 更快的 scope 停止:巢狀 scope 會在停止時一次釋放所有 effect,減少記憶體佔用。
  • 組合效能:當多個 Composable 同時運作時,Vue 只追蹤必要的訊號,跑得更快。

小結


  • onScopeDispose:composable 的清理保險絲。
  • effectScope:手動建立 / 停止一個獨立的 reactive 區域。
  • Vue 3.6:底層優化了巢狀 scope 與依賴追蹤,讓多個 composable 組裝後仍能保持高效能。

參考資料


  1. onScopeDispose() - Vue.js
  2. Preview of Vue 3.6 & Vapor Mode - Evan You
  3. Vue 與 Vite 生態最新進展:邁向一體化與智慧化的未來

上一篇
Day 17: Composable 的「可擴展設計」與 Vue 3.6 新能力
下一篇
Day 19: Vue 3.6 的秘密武器 - Vapor Mode & Alien Signals
系列文
Vue3.6 的革新:深入理解 Composition API19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言