effectscope 的核心是建立副作用生命周期边界,通过 scope.run 隔离响应式逻辑,调用 stop 自动清理;应封装进自定义 hook、绑定组件卸载、慎用嵌套与 detached 模式。

用 effectScope 封装复杂业务模块,核心不是“把代码包进去”,而是建立清晰的副作用生命周期边界——让模块内部的响应式行为、定时任务、事件监听等自动归属、统一启停,从而天然实现逻辑解耦与自动清理。
明确模块边界,用 scope.run 隔离副作用
每个业务模块(比如“实时订单监控”“用户行为埋点”“图表动画控制器”)应独立创建自己的 effectScope,并且所有副作用必须写在 scope.run(() => { ... }) 回调内。一旦脱离这个回调,watch、computed、setInterval 等就不再受控。
- 模块初始化时创建作用域:
const orderScope = effectScope() - 在 run 中注册全部响应式逻辑:
orderScope.run(() => { watch(orderId, loadDetail); setInterval(pollStatus, 3000); onScopeDispose(closeWebSocket) }) - 模块卸载或配置关闭时,只调一次
orderScope.stop(),所有监听、定时器、自定义清理函数自动执行
组合式封装:在 useXxx 函数中内置 scope
把 effectScope 写进自定义 Hook,是解决跨组件复用与清理脱节的关键。调用方无需关心怎么清,只需信任“用了就管到底”。
- Hook 内部创建并管理 scope,暴露干净的 API(如 start/stop 或直接返回状态)
- 搭配
onScopeDispose处理非 Vue 原生资源,例如:const ws = new WebSocket(url); onScopeDispose(() => ws.close()) - 避免将 stop 函数抛给使用者;若需手动控制,也应通过模块自身方法触发
scope.stop(),而非暴露原始 scope 对象
嵌套与分离:应对分层逻辑与全局依赖
模块本身可能包含子功能块(如“数据加载 + UI 动画 + 错误重试”),这时可用嵌套 effectScope 实现精细管控:
- 顶层 scope 控制整个模块生命周期,子 scope 各自负责局部逻辑,父 stop 时子自动销毁
- 对需要长期存活的依赖(如全局消息总线监听、主题切换响应),用
effectScope({ detached: true })创建独立作用域,不随组件卸载而中断 - 不同模块间共享状态但不共享副作用?用 detached scope + ref 或 provide/inject 传递信号,而非共用同一个 run 上下文
绑定组件生命周期,杜绝清理遗漏
即使用了 effectScope,不调 stop 仍等于没用。最稳妥的做法是与组件卸载强绑定:
- 在 setup 中创建 scope 后,立即用
onBeforeUnmount(() => scope.stop()) - 若模块支持动态启停(如开关按钮控制轮询),则在启用时 run,在禁用时 stop,无需等待组件销毁
- 避免在异步回调(如 Promise.then)或事件处理器中调用 scope.run —— 此时活跃作用域上下文可能已丢失,副作用无法被收集










