副作用与作用域

Vitarx 的响应式系统围绕副作用(Effect)和作用域(EffectScope)构建。副作用是响应式数据变化时自动执行的逻辑,作用域则用于批量管理副作用的生命周期。理解这两个概念,有助于你更好地控制响应式系统的行为。

Effect 基类

Effect 是所有副作用的抽象基类,定义了三种状态:

状态说明
active活跃——正常工作
paused暂停——不响应数据变化
disposed已销毁——永久停止

每个 Effect 实例提供以下方法:

  • dispose() — 销毁副作用,释放资源
  • pause() — 暂停副作用
  • resume() — 恢复副作用

以及对应的属性和钩子:

  • state — 当前状态
  • isActive / isPaused / isDisposed — 状态判断

watch() 返回的 Watcher、computed() 返回的 Computed 都是 Effect 的子类,都拥有上述能力。

EffectScope 作用域

EffectScope 是副作用作用域,用于批量管理一组 Effect 的生命周期。当作用域被销毁时,其内部的所有副作用也会一起被销毁。

createScope()

createScope() 创建一个新的作用域实例:

tsx
import { createScope, ref, watchEffect } from 'vitarx'

const scope = createScope({ name: 'my-scope' })

// 在作用域上下文中执行代码
scope.run(() => {
  const count = ref(0)

  // 此副作用会自动加入当前作用域
  watchEffect(() => {
    console.log('count:', count.value)
  })
})

scope.run(fn)

run() 在作用域上下文中执行函数。函数内创建的副作用(如 watchEffectcomputed)会自动加入该作用域:

tsx
import { createScope, ref, watchEffect, computed } from 'vitarx'

const scope = createScope()

scope.run(() => {
  const count = ref(0)

  // 这些副作用会自动加入 scope
  watchEffect(() => {
    console.log('count:', count.value)
  })

  const double = computed(() => count.value * 2)
})

scope.dispose()

dispose() 销毁作用域及其内部所有副作用:

tsx
import { createScope, ref, watchEffect } from 'vitarx'

const scope = createScope()

scope.run(() => {
  const count = ref(0)
  watchEffect(() => {
    console.log('count:', count.value)
  })
})

// 销毁作用域——内部所有副作用一起停止
scope.dispose()

scope.pause() 和 scope.resume()

pause() 暂停作用域内所有副作用,resume() 恢复:

tsx
import { createScope, ref, watchEffect } from 'vitarx'

const scope = createScope()

scope.run(() => {
  const count = ref(0)
  watchEffect(() => {
    console.log('count:', count.value)
  })
})

// 暂停——所有副作用停止响应
scope.pause()

// 恢复——所有副作用重新工作
scope.resume()

生命周期回调

EffectScope 提供了三个生命周期回调注册方法:

onScopeDispose()

作用域被销毁时执行回调:

tsx
import { createScope, onScopeDispose } from 'vitarx'

const scope = createScope()

scope.run(() => {
  onScopeDispose(() => {
    console.log('作用域被销毁了')
  })
})

scope.dispose() // 输出: "作用域被销毁了"

onScopePause()

作用域被暂停时执行回调:

tsx
import { createScope, onScopePause } from 'vitarx'

const scope = createScope()

scope.run(() => {
  onScopePause(() => {
    console.log('作用域被暂停了')
  })
})

scope.pause() // 输出: "作用域被暂停了"

onScopeResume()

作用域被恢复时执行回调:

tsx
import { createScope, onScopeResume } from 'vitarx'

const scope = createScope()

scope.run(() => {
  onScopeResume(() => {
    console.log('作用域被恢复了')
  })
})

scope.pause()
scope.resume() // 输出: "作用域被恢复了"

WARNING

onScopeDisposeonScopePauseonScopeResume 必须在作用域上下文中调用(即在 scope.run() 内部)。如果在没有活跃作用域时调用,会在控制台输出警告。可以传入第二个参数 true 来静默失败。

getActiveScope()

getActiveScope() 获取当前活跃的作用域:

tsx
import { createScope, getActiveScope } from 'vitarx'

const scope = createScope()

scope.run(() => {
  const active = getActiveScope()
  console.log(active === scope) // true
})

// 在作用域外部调用——返回 undefined
console.log(getActiveScope()) // undefined

viewEffect()

viewEffect() 是视图级副作用函数,主要用于框架内部的视图运行时(如 DOM 更新、指令副作用等)。它的特点是支持暂停和恢复——当组件不可见时暂停,可见时恢复。

tsx
import { ref, viewEffect } from 'vitarx'

const count = ref(0)

// 创建视图级副作用
const effect = viewEffect(() => {
  console.log('DOM 更新相关逻辑, count:', count.value)
})

if (effect) {
  // 暂停——组件不可见时
  effect.pause()

  // 恢复——组件可见时
  effect.resume()

  // 销毁——组件卸载时
  effect.dispose()
}

WARNING

viewEffect() 主要服务于框架内部的视图运行时。业务代码通常应该使用 watchEffect() 代替。使用 viewEffect() 时需要在合适的时机主动调用 dispose(),否则可能造成内存泄漏。

viewEffect 的返回值

  • 如果副作用函数内有信号依赖,返回一个包含 pause()resume()dispose() 方法的控制对象
  • 如果没有信号依赖,返回 null

作用域与组件

在 Vitarx 中,每个组件实例都会创建一个 EffectScope。组件内的 watchEffectcomputed 等副作用会自动加入组件的作用域。当组件卸载时,作用域被销毁,所有副作用自动清理。

这意味着你通常不需要手动管理作用域——框架已经帮你处理好了。但在以下场景中,手动使用作用域会很有用:

  • 在组件外部创建响应式逻辑(如全局状态管理)
  • 需要批量暂停/恢复一组副作用
  • 需要在特定时机统一释放资源

完整示例

下面是一个综合使用 EffectScope 相关 API 的示例:

tsx
import {
  createScope,
  onScopeDispose,
  onScopePause,
  onScopeResume,
  ref,
  watchEffect,
  computed,
  createApp
} from 'vitarx'

function App() {
  // 创建独立作用域
  const scope = createScope({ name: 'counter-scope' })

  const count = ref(0)
  const double = computed(() => count.value * 2)

  // 在作用域中执行
  scope.run(() => {
    // 注册生命周期回调
    onScopeDispose(() => {
      console.log('计数器作用域已销毁')
    })

    onScopePause(() => {
      console.log('计数器作用域已暂停')
    })

    onScopeResume(() => {
      console.log('计数器作用域已恢复')
    })

    // 此副作用自动加入作用域
    watchEffect(() => {
      console.log('count 变化了:', count.value)
    })
  })

  const increment = () => count.value++
  const handlePause = () => scope.pause()
  const handleResume = () => scope.resume()
  const handleDispose = () => scope.dispose()

  return (
    <div style={{ padding: '20px' }}>
      <h2>副作用与作用域示例</h2>

      <div style={{ marginTop: '16px' }}>
        <p>计数: {count}</p>
        <p>双倍: {double}</p>
        <p>作用域状态: {scope.state}</p>
        <p>副作用数量: {scope.count}</p>
      </div>

      <div style={{ marginTop: '16px', display: 'flex', gap: '8px' }}>
        <button onClick={increment}>+1</button>
        <button onClick={handlePause}>暂停作用域</button>
        <button onClick={handleResume}>恢复作用域</button>
        <button onClick={handleDispose}>销毁作用域</button>
      </div>
    </div>
  )
}

createApp(App).mount('#app')

下一步

  • ref 详解 — 回顾 ref 的各种变体和工具函数
  • watch 详解 — 深入了解 watch 和 watchEffect 的用法