阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Effect作用域API

Effect作用域API

作者:陈川 阅读数:15025人阅读 分类: Vue.js

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')

// 在开发者工具中会显示这个名称

限制与注意事项

  1. 一个作用域只能被停止一次
  2. 停止后的作用域不能再运行新的副作用
  3. 嵌套作用域的停止是级联的
  4. 在SSR环境中需要特别注意作用域的生命周期

与其他API的对比

与直接使用watchwatchEffect相比,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()

最佳实践建议

  1. 为每个逻辑功能单元创建独立的作用域
  2. 在可组合函数中返回stop方法
  3. 给作用域添加有意义的名称便于调试
  4. 避免在停止的作用域中运行新的副作用
  5. 考虑使用onScopeDispose替代直接的onUnmounted
import { onScopeDispose } from 'vue'

const scope = effectScope()

scope.run(() => {
  onScopeDispose(() => {
    // 作用域特有的清理逻辑
  })
})

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌