Effect作用域API
Effect作用域API简介
Vue 3的Effect作用域API提供了一种组织和管理副作用的方式。它允许开发者创建独立的副作用作用域,并在需要时手动停止或暂停这些副作用。这个API特别适合在组件卸载时清理副作用,或者在需要精确控制副作用执行时机时使用。
基本用法
创建一个effect作用域非常简单,使用effectScope()
函数即可:
import { effectScope } from 'vue'
const scope = effectScope()
在这个作用域内,可以运行多个副作用:
scope.run(() => {
const state = reactive({ count: 0 })
watchEffect(() => {
console.log(state.count)
})
watch(() => state.count, (newVal) => {
console.log('Count changed:', newVal)
})
})
停止作用域
当不再需要作用域内的副作用时,可以调用stop()
方法停止所有副作用:
scope.stop()
这相当于手动调用了每个副作用的清理函数。在组件卸载时特别有用:
import { onUnmounted } from 'vue'
onUnmounted(() => {
scope.stop()
})
嵌套作用域
Effect作用域可以嵌套使用,子作用域会继承父作用域的行为:
const parentScope = effectScope()
parentScope.run(() => {
const childScope = effectScope()
childScope.run(() => {
// 这里的副作用属于childScope
})
// 停止父作用域也会停止子作用域
parentScope.stop()
})
自动收集依赖
在作用域内创建的响应式依赖会被自动收集:
const scope = effectScope()
scope.run(() => {
const state = reactive({
name: 'Vue',
version: 3
})
// 这个计算属性会被自动收集
const info = computed(() => `${state.name} ${state.version}`)
watchEffect(() => {
console.log(info.value)
})
})
实际应用场景
组件内使用
在组件中使用effect作用域可以更好地管理副作用:
import { effectScope, onUnmounted } from 'vue'
export default {
setup() {
const scope = effectScope()
scope.run(() => {
// 所有setup中的副作用
const state = reactive({ /* ... */ })
watchEffect(/* ... */)
// ...
})
onUnmounted(() => scope.stop())
return { /* ... */ }
}
}
可组合函数
在可组合函数中使用effect作用域:
function useFeature() {
const scope = effectScope()
const state = reactive({ /* ... */ })
scope.run(() => {
watchEffect(/* ... */)
// 其他副作用
})
return {
...toRefs(state),
stop: () => scope.stop()
}
}
高级用法
分离作用域
可以将某些副作用分离到独立的作用域中:
const mainScope = effectScope()
const analyticsScope = effectScope()
mainScope.run(() => {
// 主逻辑副作用
})
analyticsScope.run(() => {
// 分析相关的副作用
})
// 可以单独停止分析相关副作用
analyticsScope.stop()
作用域状态
可以检查作用域的当前状态:
const scope = effectScope()
console.log(scope.active) // true
scope.stop()
console.log(scope.active) // false
与Suspense集成
Effect作用域可以与Suspense配合使用:
const scope = effectScope()
async function setup() {
scope.run(() => {
// 异步副作用
})
return scope
}
// 在Suspense边界内
const result = await setup()
// 当Suspense解析完成后
result.stop()
性能考虑
使用effect作用域可以优化性能:
// 在需要时创建作用域
const heavyComputationScope = effectScope()
function startHeavyComputation() {
heavyComputationScope.run(() => {
// 执行计算密集型操作
})
}
function stopHeavyComputation() {
heavyComputationScope.stop()
}
与Pinia集成
在状态管理库Pinia中使用effect作用域:
import { defineStore } from 'pinia'
export const useStore = defineStore('main', () => {
const scope = effectScope()
const state = reactive({ /* ... */ })
scope.run(() => {
// store的副作用
watchEffect(/* ... */)
})
return {
...toRefs(state),
stop: () => scope.stop()
}
})
调试技巧
可以通过给作用域命名来方便调试:
const scope = effectScope('ComponentScope')
// 在开发者工具中会显示这个名称
限制与注意事项
- 一个作用域只能被停止一次
- 停止后的作用域不能再运行新的副作用
- 嵌套作用域的停止是级联的
- 在SSR环境中需要特别注意作用域的生命周期
与其他API的对比
与直接使用watch
和watchEffect
相比,effect作用域提供了更集中的管理方式:
// 传统方式
const stop1 = watchEffect(/* ... */)
const stop2 = watch(/* ... */)
// 需要手动停止每个
stop1()
stop2()
// 使用作用域
const scope = effectScope()
scope.run(() => {
watchEffect(/* ... */)
watch(/* ... */)
})
// 一键停止所有
scope.stop()
源码解析
Effect作用域的实现原理主要基于Vue的响应式系统:
class EffectScope {
constructor(detached = false) {
this.active = true
this.effects = []
this.cleanups = []
// ...
}
run(fn) {
if (this.active) {
try {
return fn()
} finally {
// 清理
}
}
}
stop() {
if (this.active) {
this.active = false
this.effects.forEach(effect => effect.stop())
this.cleanups.forEach(cleanup => cleanup())
// ...
}
}
}
测试策略
测试effect作用域时可以考虑以下方面:
import { effectScope, watchEffect } from 'vue'
test('effect scope basic usage', () => {
const scope = effectScope()
let count = 0
scope.run(() => {
watchEffect(() => {
count++
})
})
expect(count).toBe(1)
scope.stop()
// 验证副作用是否已停止
})
迁移指南
从Vue 2迁移到Vue 3时,可以使用effect作用域重构原有的代码:
// Vue 2方式
export default {
created() {
this._watchers = []
this._watchers.push(this.$watch(/* ... */))
},
beforeDestroy() {
this._watchers.forEach(unwatch => unwatch())
}
}
// Vue 3方式
export default {
setup() {
const scope = effectScope()
scope.run(() => {
watch(/* ... */)
// 其他副作用
})
onUnmounted(() => scope.stop())
}
}
常见问题解决
作用域未正确停止
确保在组件卸载时停止作用域:
// 错误方式 - 可能内存泄漏
const scope = effectScope()
scope.run(/* ... */)
// 正确方式
const scope = effectScope()
onUnmounted(() => scope.stop())
scope.run(/* ... */)
异步副作用处理
处理异步副作用时需要注意:
const scope = effectScope()
async function loadData() {
const data = await fetchData()
// 确保作用域仍处于活动状态
if (scope.active) {
scope.run(() => {
// 使用data的副作用
})
}
}
loadData()
最佳实践建议
- 为每个逻辑功能单元创建独立的作用域
- 在可组合函数中返回stop方法
- 给作用域添加有意义的名称便于调试
- 避免在停止的作用域中运行新的副作用
- 考虑使用
onScopeDispose
替代直接的onUnmounted
import { onScopeDispose } from 'vue'
const scope = effectScope()
scope.run(() => {
onScopeDispose(() => {
// 作用域特有的清理逻辑
})
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn