effectScope 创建一个 effect 作用域

简介: 当在单独的文件中,我们该如何停止 computed & watch 的响应式依赖呢?

在 Vue 的 setup 中,响应会在开始初始化的时候被收集,在实例被卸载的时候,响应就会自动的被取消追踪了,这时一个很方便的特性。

但是,当我们在组件外使用或者编写一个独立的包时,这会变得非常麻烦。当在单独的文件中,我们该如何停止 computed & watch 的响应式依赖呢?

const scope = effectScope()

scope.run(() => {
  const doubled = computed(() => counter.value * 2)

  watch(doubled, () => console.log(doubled.value))

  watchEffect(() => console.log('Count: ', doubled.value))
})

// 处理掉当前作用域内的所有 effect
scope.stop()

该特性就是试图将组件的 setup() 响应式依赖收集和处理功能抽象为更通用的 API,该 API 可以在组件模型之外复用。

使用 effectScope() 创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。

使用 getCurrentScope() 返回当前活跃的 effect 作用域。

使用 onScopeDispose() 方法可以在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。

这个方法可以作为可复用的组合式函数中 onUnmounted 的替代品,它并不与组件耦合,因为每一个 Vue 组件的 setup() 函数也是在一个 effect 作用域中调用的。

鼠标跟踪器示例

使用组合式 API 实现鼠标跟踪功能,它会是这样的:

// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
  // 被组合式函数封装和管理的状态
  const x = ref(0)
  const y = ref(0)

  // 组合式函数可以随时更改其状态。
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // 一个组合式函数也可以挂靠在所属组件的生命周期上
  // 来启动和卸载副作用
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // 通过返回值暴露所管理的状态
  return { x, y }
}

如果在多个组件中调用 useMouse () ,则每个组件将附加一个 mouseemove 监听器。我们应该能够通过在多个组件之间共享相同的侦听器集和 refs 来提高效率,但是我们做不到,因为每个 onUnmounted 调用都耦合到一个组件实例。

我们可以使用分离作用域和 onScopeDispose 来实现这一点, 首先,我们需要用 onScopeDispose 替换 onUnmounted

- onUnmounted(() => {
+ onScopeDispose(() => {
  window.removeEventListener('mousemove', handler)
})

这仍然有效,因为 Vue 组件现在也在作用域内运行其 setup () ,该作用域将在组件卸载时释放。

然后,我们可以创建一个工具函数来管理父范围订阅:

function createSharedComposable(composable) {
  let subscribers = 0
  let state, scope

  const dispose = () => {
    if (scope && --subscribers <= 0) {
      scope.stop()
      state = scope = null
    }
  }
    
  // 这里只有在第一次运行的时候创建一个state, 后面所有的组件就不会再创建新的state,而是共用一个state
  return (...args) => {
    subscribers++
    if (!state) {
      scope = effectScope(true)
      state = scope.run(() => composable(...args))
    }
    onScopeDispose(dispose)
    return state
  }
}

现在我们就可以使用这个 shared 版本的 useMouse

const useSharedMouse = createSharedComposable(useMouse)

通过这个例子,不禁想到,是否可以通过这种模式模拟 vuex 的能力?我们是否可以通过 shared composables 更加灵活的达到全局状态管理的目的呢?

export const useGlobalState = createSharedComposable(
  () => {
    const count = ref(0)
    return { count }
  }
)

VueUse

createSharedComposable 方法 VueUse 库也提供了,让我们看看源码。


import { getCurrentScope, onScopeDispose, effectScope } from 'vue'

export function createSharedComposable(composable) {
  let subscribers = 0
  let state
  let scope

  const dispose = () => {
    subscribers -= 1
    if (scope && subscribers <= 0) {
      // 取消追踪
      scope.stop()
      state = undefined
      scope = undefined
    }
  }

  return ((...args) => {
    subscribers += 1
    if (!state) {
      // 创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。
      scope = effectScope(true)
      state = scope.run(() => composable(...args))
    }
    tryOnScopeDispose(dispose)
    return state
  })
}

function tryOnScopeDispose(fn) {
  // 返回当前活跃的 effect 作用域。
  if (getCurrentScope()) {
    // 在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。
    onScopeDispose(fn)
    return true
  }
  return false
}
相关文章
|
7月前
|
编译器 C++
c++关于命名空间内变量和函数及全局变量的使用和作用域
c++关于命名空间内变量和函数及全局变量的使用和作用域
114 1
|
7月前
|
自然语言处理 JavaScript 前端开发
作用域的概念及作用?作用域的分类?.js 属于哪种作用域?
作用域的概念及作用?作用域的分类?.js 属于哪种作用域?
72 0
|
4月前
|
JavaScript 前端开发
|
4月前
|
JavaScript 前端开发
理解全局作用域
【8月更文挑战第25天】
30 0
|
7月前
|
JavaScript
JS作用域(全局作用域+局部作用域)
JS作用域(全局作用域+局部作用域)
35 0
|
7月前
|
存储 缓存 编译器
learn_C_deep_2 (作用域和生命周期、局部变量和全局变量、最宽宏大量的关键字 - auto、最快的关键字 - register)
learn_C_deep_2 (作用域和生命周期、局部变量和全局变量、最宽宏大量的关键字 - auto、最快的关键字 - register)
|
JavaScript 算法
js中函数内部属性arguments和this以及方法apply()和call()
js中函数内部属性arguments和this以及方法apply()和call()
|
PHP 开发者
超全局作用域|学习笔记
快速学习超全局作用域
超全局作用域|学习笔记
|
存储 Python
Python函数作用域与命名空间
Python函数作用域与命名空间
【Groovy】闭包 Closure ( 闭包的 delegate 代理策略 | OWNER_FIRST | DELEGATE_FIRST | OWNER_ONLY | DELEGATE_ONLY )
【Groovy】闭包 Closure ( 闭包的 delegate 代理策略 | OWNER_FIRST | DELEGATE_FIRST | OWNER_ONLY | DELEGATE_ONLY )
147 0
【Groovy】闭包 Closure ( 闭包的 delegate 代理策略 | OWNER_FIRST | DELEGATE_FIRST | OWNER_ONLY | DELEGATE_ONLY )