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
}
相关文章
|
8月前
|
编译器 C++
c++关于命名空间内变量和函数及全局变量的使用和作用域
c++关于命名空间内变量和函数及全局变量的使用和作用域
124 1
|
4月前
|
Java
作用域
作用域
26 2
|
4月前
C 作用域详解
在 C 语言中,作用域决定了变量和函数的可见性和生命周期,包括块作用域、函数作用域、文件作用域和全局作用域。块作用域内的变量仅在块内有效,函数作用域内的变量在整个函数内有效,文件作用域内的全局变量和函数在整个文件内有效,而全局作用域内的变量和函数在整个程序运行期间有效。作用域的优先级遵循局部变量优先的原则,局部变量会遮蔽同名的全局变量。变量的生命周期分为局部变量(函数调用时创建和销毁)、全局变量(程序开始时创建和结束时销毁)以及静态变量(整个程序期间有效)。理解作用域有助于避免命名冲突和错误,提高代码的可读性和可维护性。
|
5月前
|
JavaScript
Redux 中定义 Action
【8月更文挑战第30天】
67 0
|
5月前
|
JavaScript 前端开发
理解全局作用域
【8月更文挑战第25天】
35 0
|
8月前
|
C语言
c作用域规则
c作用域规则
75 0
|
8月前
|
JavaScript 前端开发
js开发:请解释什么是作用域(scope),并说明全局作用域、局部作用域和块级作用域的区别。
JavaScript中的作用域规定了变量和函数的可见性与生命周期。全局作用域适用于整个脚本,变量可通过全局对象访问,可能导致命名冲突和内存占用。局部作用域限于函数内部,每次调用创建新作用域,执行完毕后销毁。ES6引入的块级作用域通过`let`和`const`实现,变量仅在其代码块内有效,并有暂时性死区。作用域机制有助于代码组织和变量管理。
59 1
|
8月前
|
算法 C语言 C++
【C/C++ 关键字 类型限定符 】 C/C++ 中 const的用法:限制变量的作用域和可见性
【C/C++ 关键字 类型限定符 】 C/C++ 中 const的用法:限制变量的作用域和可见性
62 0
|
8月前
|
存储 缓存 编译器
learn_C_deep_2 (作用域和生命周期、局部变量和全局变量、最宽宏大量的关键字 - auto、最快的关键字 - register)
learn_C_deep_2 (作用域和生命周期、局部变量和全局变量、最宽宏大量的关键字 - auto、最快的关键字 - register)
|
存储 搜索推荐 C语言
C 作用域规则
C 作用域规则
57 0

热门文章

最新文章