effectscope 通过 scope.run() 创建执行上下文,使其中的 watcheffect 自动归属并受控于该作用域;调用 scope.stop() 可一键停止所有相关副作用并自动触发 oncleanup。

watchEffect 自动纳入作用域的原理
Vue 的响应式系统在 effectScope 激活期间(即 scope.run 回调执行时),会将当前活跃作用域设为“默认归属目标”。所有在此期间调用的 watchEffect(以及 watch、computed、effect)都会内部调用 registerRunningWatchEffect 或类似逻辑,把自己登记进当前 scope 的副作用列表中。
这意味着你不需要显式传入 scope,只要确保 watchEffect 写在 scope.run(() => { ... }) 里面,它就天然受控。
典型写法:批量声明 + 一键停止
以下是一个组件 setup 中的标准模式:
✅ 正确示例:
- 创建作用域:
const scope = effectScope() - 在 run 中定义多个 watchEffect:
scope.run(() => {
watchEffect(() => console.log('A:', count.value))
watchEffect(() => console.log('B:', name.value))
watchEffect((onCleanup) => {
const id = setTimeout(() => console.log('timeout'), 1000)
onCleanup(() => clearTimeout(id))
})}) - 卸载时统一停止:
onUnmounted(() => scope.stop())
调用 scope.stop() 后,上述三个 watchEffect 全部失效:不再响应数据变化、不执行回调、onCleanup 会被自动触发(如果已注册)。
注意 cleanup 回调仍按预期工作
即使 watchEffect 被 scope.stop() 停止,其内部通过 onCleanup 注册的清理函数依然会执行一次——这是 Vue 的保障机制。例如上面的定时器,会在 stop 时被 clearTimeout,避免内存泄漏。
不需要也不应该在 stop 前手动调用 onCleanup;它由 Vue 在副作用销毁阶段自动调度。
脱离组件场景也完全适用
effectScope 不依赖组件实例,所以在独立工具函数、插件或非 setup 环境中同样有效:
- 封装一个带自动清理的轮询函数:
export function usePolling(url, interval) {
const scope = effectScope();
scope.run(() => {
watchEffect(async (onCleanup) => {
const controller = new AbortController();
onCleanup(() => controller.abort());
await fetch(url, { signal: controller.signal });
});
setInterval(() => {}, interval); // 可配合 onScopeDispose 清理
});
return () => scope.stop();}
调用方只需保存返回的 stop 函数,在需要时调用即可释放全部 watchEffect 和关联资源。











