Vue3组合式函数中的响应式泄漏:onScopeDispose没有触发cleanup的根因

问题场景

在Vue3项目中使用组合式函数(Composable)封装副作用逻辑时,组件卸载后副作用仍在执行。典型代码如下:

export function usePolling(fetchFn, interval = 3000) {
  const data = ref(null);
  let timer = setInterval(() => {
    fetchFn().then(res => data.value = res);
  }, interval);
  onScopeDispose(() => clearInterval(timer));
  return { data };
}

在组件中使用这个Composable,切换路由后组件卸载,但setInterval没有被清除,定时请求仍在持续发送。控制台也没有报错,问题很难定位。

根因:活跃的EffectScope未销毁

onScopeDispose的触发条件是当前EffectScope被销毁。组件的setup()函数默认运行在组件的EffectScope中,组件卸载时该scope会被自动销毁,onScopeDispose回调正常执行。

问题出在异步调用场景。如果Composable在async setup()中使用,或者在setTimeout/Promise.then的回调中调用,此时已经脱离了组件的EffectScope:getCurrentScope()返回nullonScopeDispose注册的回调实际上无处挂载。这意味着组件卸载时,没有scope来触发这个cleanup。

另一种常见场景是在Pinia Store的action中调用Composable。Pinia的action不在组件的EffectScope内执行,onScopeDispose同样不会触发。

解决方案:显式管理副作用生命周期

方案一:返回cleanup函数,由调用方手动执行

export function usePolling(fetchFn, interval = 3000) {
  const data = ref(null);
  let timer = setInterval(() => {
    fetchFn().then(res => data.value = res);
  }, interval);
  const stop = () => {
    clearInterval(timer);
    timer = null;
  };
  if (getCurrentScope()) {
    onScopeDispose(stop);
  }
  return { data, stop };
}

组件中使用onUnmounted调用stop(),双重保险。

方案二:用watchEffect替代setInterval

把轮询逻辑改为watchEffect+手动递归setTimeout的方式,watchEffect会在scope销毁时自动停止。不过这种方式实现轮询需要额外处理首次执行和重试逻辑,代码复杂度增加。

方案三:封装通用的副作用收集器

export function useCleanup() {
  const effects = [];
  const add = (fn) => effects.push(fn);
  const run = () => effects.forEach(fn => fn());
  if (getCurrentScope()) onScopeDispose(run);
  return { add, run };
}

Composable内部用cleanup.add(() => clearInterval(timer))注册,组件卸载时调用cleanup.run()。这种方式适合一个组件内使用多个Composable的场景,统一管理所有副作用的清理。

排查响应式泄漏的调试方法

Vue DevTools的组件面板可以看到每个组件的EffectScope数量。如果一个组件卸载后其scope内的watcher或computed仍在运行,DevTools会在组件面板中显示已卸载组件的残留副作用。

更直接的方式是用Chrome DevTools的Performance面板录制一段时间线,切到对应路由后再切回,观察内存曲线是否持续上升。如果上升,在Memory面板中拍快照,搜索ReactiveEffect对象数量,数量不减说明有响应式泄漏。

预防措施

在项目规范中约定:所有Composable必须返回stopdispose方法,不依赖onScopeDispose作为唯一的cleanup机制。ESLint可以写一条自定义规则,检测Composable中使用了onScopeDispose但没有导出cleanup函数的情况,在CI阶段拦截这类问题。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-han-shu-zhong-de-xiang-ying-shi-xie-lou/

(0)
小编小编
上一篇 14小时前
下一篇 14小时前

相关推荐