前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库Vue effectScope 跨组件 共享 单例 副作用
VuVue侦听与生命周期

如何用 Vue effectScope 实现跨组件共享且可回收的副作用?

共享的目标是多个消费者复用一份工作,同时仍然知道最后由谁关掉它。

前端进阶之旅 · 一题精讲更新于 2026.09.06
Vue#侦听与生命周期
先看核心答案读代码示例
理解线索

共享服务的四个状态动作

  1. acquire 与 reuse增加消费者计数,必要时创建实例并复用状态
  2. release每个消费者最多归还一次引用
  3. dispose计数归零时 stop 并清除当前实例

引用计数管理寿命,不负责用户权限、缓存一致性或不同参数实例的键控策略。

核心回答

先记住这个答案

可以为共享服务建立 detached effectScope,在第一个消费者到来时创建一次 watch 或全局订阅,后续消费者复用同一状态;每个消费者离开时减少引用,归零后 stop scope 并清空实例。release 必须幂等,下一次消费者到来要新建 scope。浏览器应用可按 app 创建容器,SSR 则应按请求或应用实例隔离,不能把带用户信息的可变单例放在模块顶层跨请求复用。

  • 首个消费者启动,后续消费者复用
  • 最后消费者释放时停止 detached scope
  • 服务端共享状态必须按请求或应用隔离

先确定共享键和容器边界

如果一个组件要跟踪窗口尺寸,多个消费者通常可以共用同一份浏览器监听。如果函数参数包含房间编号或用户身份,则不能把所有调用都塞进同一个实例,应按稳定键保存多个服务并分别计数。

SSR 中应由每次 createApp 或请求上下文持有这张实例表。模块顶层变量在长驻进程里可能服务后续请求,使一个用户的状态被另一个请求读到;这不是 effectScope 能自动解决的隔离问题。

用幂等 release 结束最后一份引用

下例展示单键共享容器的核心寿命逻辑。第一次 acquire 创建 detached scope 并在其中建立效果,第二次只增加计数;每个返回的 release 用局部布尔值保证最多减少一次。

归零时先保存并清除全局当前引用,再停止旧 scope,可以降低清理回调重入时误拿旧实例的风险。生产代码还应处理初始化抛错:只有创建成功后才增加可对外释放的引用。

首个启动与末个释放TypeScript
import { effectScope, ref, watchEffect } from 'vue'

const source = ref(0)
let current: { scope: ReturnType<typeof effectScope>, users: number, seen: number[] } | undefined
function acquireShared() {
  if (!current) {
    const scope = effectScope(true)
    const seen: number[] = []
    scope.run(() => watchEffect(() => seen.push(source.value)))
    current = { scope, users: 0, seen }
  }
  const service = current
  service.users++
  let released = false
  return { service, release() {
    if (released) return
    released = true
    service.users--
    if (service.users === 0 && current === service) {
      current = undefined
      service.scope.stop()
    }
  }}
}

两个消费者会拿到同一 service;释放第一个后效果仍活动,第二个释放后 scope 停止。重复调用同一 release 不会让计数为负,下一次 acquire 会建立全新实例。

与组件生命周期正确衔接

组件 setup 中 acquire 后,可以通过 onScopeDispose 登记这次消费者的 release。共享 scope 自身保持 detached,不会因为任意一个组件先卸载就结束;只有所有登记的消费者都释放才关闭。

还要考虑 KeepAlive:失活是否释放取决于服务是否应在缓存期间继续工作。如果只在可见时需要,应在 activated 与 deactivated 管理订阅;若缓存组件仍算活跃消费者,则让最终卸载负责释放。

回答前,多想一步

容易答错的地方

每次调用都创建 detached scope
这样虽然每个组件都能工作,却没有实现共享,且一旦漏掉停止就会累积全局监听。应将实例保存在明确容器中,只让首次 acquire 负责创建。
release 非幂等导致提前关闭
组件清理、错误回退和手动关闭可能走多条路径。若同一消费者重复减计数,其他仍在线的组件会突然失去服务;局部 released 标记能封住这类重复释放。
试着用自己的话回答

面试官还会怎么问?

不同参数的共享服务如何管理?

用规范化后的参数作为键,Map 中每个键保存独立 scope、状态和引用数。release 时只修改相应条目,归零停止并删除;对象键还要明确身份语义和回收策略。

为什么不用组件 provide 和 inject 就够了?

provide 可以确定容器边界并分发服务,很适合每个 app 或子树一份实例;effectScope 继续负责服务内部多个响应式效果的统一停止。两者解决分发与生命周期两个层面,可以组合使用。

共享效果停止后状态要保留吗?

按业务决定。若下次进入需要新鲜订阅,清除实例最清楚;若要保留只读缓存,可把缓存与活动 scope 分开保存,但必须避免旧缓存带用户身份跨请求泄漏。

从一道题,走向一组知识

把知识连起来

侦听与生命周期

Vue effectScope(true) 为什么容易泄漏,什么时候才该使用?

共享服务依赖分离作用域,也因此必须自己定义结束责任。

侦听与生命周期

Vue getCurrentScope 在组合式函数中有什么用途?

消费者可在当前组件作用域登记幂等 release。

侦听与生命周期

Vue 3.5 如何暂停与恢复 watch,pause 和 stop 有什么区别?

临时暂停消费者需求与最后引用释放是两种不同生命周期动作。

参考资料

  • Vue RFC:Shared Composable
  • Vue:effectScope API

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 先确定共享键和容器边界
  3. 用幂等 release 结束最后一份引用
  4. 与组件生命周期正确衔接
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑