先记住这个答案
getCurrentScope 返回当前活动的 effect scope,没有则返回 undefined。组合式函数可以据此判断自己是否运行在组件 setup 或 scope.run 的同步上下文中,从而安全登记 onScopeDispose。若函数必须由受管作用域调用,可以在缺失时先报错再创建资源;若允许独立使用,则应返回手动 dispose,并只在存在 scope 时额外登记自动清理。检查必须发生在实际创建与登记时,异步等待前取得的 scope 引用不能让等待后的执行重新变成活动上下文。
- 返回值表示调用当下的活动 scope
- 先检查 owner 再创建有寿命的资源
- 支持无 scope 时要暴露手动释放路径
当前意味着正在同步执行的 owner
在 scope.run 的回调或组件同步 setup 中调用,可以取得相应 scope。run 返回之后再调用通常没有当前作用域;即便闭包还保存某个 scope 对象,它也不等于此刻处于活动状态。
因此不要在异步任务开始前保存 getCurrentScope 的结果,等待后再调用 onScopeDispose 并期望自动登记回去。应在同步阶段建立清理关系,或直接通过明确参数和返回值传递释放责任。
为可复用计时器提供明确合同
下面的 useManagedTimer 要求存在 owner,先检查再创建定时器。这样模块顶层误用会立即得到清楚错误,而不是悄悄启动一个永不结束的任务。
示例中的 setInterval 属于宿主环境资源;effect scope 不会自动识别它。真正让它随 scope 结束的是 onScopeDispose 中的 clearInterval,检查 API 只负责保证清理有地方登记。
import { getCurrentScope, onScopeDispose } from 'vue'
function useManagedTimer(task: () => void, delay: number) {
if (!getCurrentScope()) {
throw new Error('useManagedTimer must run inside an active Vue scope')
}
const timer = setInterval(task, delay)
onScopeDispose(() => clearInterval(timer))
return () => clearInterval(timer)
}在 effectScope.run 或组件 setup 中调用会创建计时器并登记释放;直接在普通模块代码调用会在 setInterval 之前抛错,因此不会先制造一个无 owner 的资源。
允许独立使用时返回 dispose
有些库既服务 Vue 组件,也服务普通脚本。可以总是返回关闭函数,存在 current scope 时再自动登记;调用者在无 scope 环境中必须保存并调用它,文档和类型应明确这份责任。
如果调用方忽略 dispose,API 无法仅靠 getCurrentScope 补救。可考虑把独立模式设计为显式函数名或必须传 owner,避免一个看似自动管理的函数在不同上下文中悄悄改变清理保证。
容易答错的地方
- 检查通过后越过 await 再登记
- 检查只描述当时的活动状态,不会把上下文延长到异步继续。应在同步阶段登记能够使任务失效的清理,异步取得资源后再检查该失效标记并立即释放迟到资源。
- 没有 scope 就内部创建永久 detached
- 这会把调用错误变成隐藏的全局寿命,并把释放责任藏在库内部。除非 API 明确提供独立服务容器和 close,否则应报错或返回手动 dispose。
面试官还会怎么问?
getCurrentScope 能拿到组件实例吗?
不能把它当成组件实例查找 API。它返回效果作用域,用于响应式效果和清理归属;组件属性、插槽或公开实例应使用对应的组件上下文接口。
为什么 composable 在事件回调里调用会失败?
事件发生时 setup 的同步执行早已结束,当前作用域通常不存在。应在 setup 中创建 composable,把事件只用于调用它已经返回的操作,而不是点击后才建立生命周期资源。
库函数应该报错还是降级?
取决于公开合同。必须依赖自动销毁的函数应尽早报错;支持普通脚本的函数可返回 dispose 并在有 scope 时附加自动清理,但不能静默丢失唯一释放路径。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。