如何理解Vue中的effectscope?

How to understand the effectscope in Vue?

官方RFC

有一个例子 effect

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

  const dispose = () => {
    if (scope && --subscribers <= 0) {
      scope.stop()
      state = scope = null
    }
  }

  return (...args) => {
    subscribers++
    if (!state) {
      scope = effectScope(true)
      state = scope.run(() => composable(...args))
    }
    onScopeDispose(dispose)
    return state
  }
}

我知道它会做什么,当我们使用 useMouse API

时,它会强制所有组件只计算一次

但是我无法理解effect的概念,它是如何工作的?

尤其是 effect 的一些 API,例如 getCurrentScope。我试图查看 getCurrentScope 的 return 值,但我一无所获。

请帮帮我!

用代码制作咖啡会是什么样子?

snowingfox.getCupsOutOfCupboard();
snowingfox.getCoffeeOffShelf();
snowingfox.getMilkOutOfFridge();
snowingfox.boilingWater();
// ...

现在想象一下,每天早上我醒来喝杯咖啡。你可以说我在做 一杯咖啡作为醒来的反应。我怎么会运行这段代码在 对 isMorning 变量变为 true?

的响应

这就是 effect 在 Vue 3 中解决的问题。它围绕着一大块 应响应响应数据更改而执行的代码。实际上,您很可能不会直接使用 effect,而是依赖 computedwatchEffect 之类的东西(在他们的代码中使用 effect 实施)。

简而言之effect是Vue反应系统的一部分,是Vue的反应方式 标记和定位应重新运行 以响应数据更新的代码。

文档https://v3.vuejs.org/guide/reactivity.html

课程https://www.vuemastery.com/courses/vue-3-reactivity/vue3-reactivity/

以下是如何将初始代码实现为响应式的:

import { ref, watchEffect } from 'vue';

const isMorning = ref(false);

watchEffect(() => {
    if (!isMorning.value) return;
    
    snowingfox.getCupsOutOfCupboard();
    snowingfox.getCoffeeOffShelf();
    snowingfox.getMilkOutOfFridge();
    snowingfox.boilingWater();
});

effect 是反应式框架(VueJS 和 React)中常用的术语,指的是(我相信)side effect。如果您熟悉函数式编程,您可能已经知道它被称为 side effect 因为它不是“纯函数”,因为它改变了共享或全局状态。

忽略学术术语,effect 在这些系统中仅指任何应用程序定义的方法,做一些定制的事情,比如

const foo = () => {
  // I do something bespoke
}

effect 的含义真是太广泛了。您的方法在其主体中实际执行的操作与框架无关。框架所知道的只是 foo 做一些非结构化的事情。 VueJS 额外做的是通过它的反应系统来监控你的效果是否依赖于任何反应数据。如果是这样,VueJS 将在每​​次它依赖的数据发生变化时重新运行你的效果。

effect(或副作用)不是什么坏事、特殊事或高级事。事实上,您的应用程序就是制作 effects/side 效果。例如,VueJS 应用程序中最常见的效果是 DOM 操作 。它是如此常见以至于 VueJS 将它提取到一个不同的抽象中:template。在幕后,模板被编译为呈现函数——看起来很像上面的 foo——每当一些相关的反应数据发生变化时,这些函数就会重新评估。这就是 VueJS 让您的 UI 保持最新状态的方式。

常见效果的另一个极端是那些真正定制的效果,例如每当数据发生变化时,您只想做一些老式的命令式操作(例如 jQuery 样式)。而 VueJS 让你通过 watchEffect 做到:你给 VueJS 一个 lambda,VueJS 会在每次它的依赖改变时盲目调用它,而不问它在做什么。

VueJS discovers your dependency on reactive data by running your effect. As long as your effect accesses any reactive data (say, yourState.bar) during its execution, VueJS will notice that and record a dependency of your effect on yourState.bar

从本质上讲,反应系统只是古老的 observable/subscriber 模式的现代版本。反应状态是可观察的,效果是 subscribers/observers。如果你超越魔法层并以订阅者模式的形式考虑 VueJS,那么有一个问题是它无法避免的:每当你有 subscribe 时,你将不得不处理 [=17] =],否则你将有内存或资源泄漏,仅仅是因为你一直持有订阅者(他们反过来持有其他东西)并且没有任何东西可以被释放。这个退订部分就是 RFC 所说的“effect dispose”。

通常,在处理此 unsubscribing/disposing/cleaning up/cancelling 业务时,您会遇到两个挑战:

  • 决定何时到unsubscribe/dispose
  • 知道如何到unsubscribe/dispose

在典型的响应式框架中,以上两者都是应用程序的职责。作为应用程序开发者,您是唯一知道何时不再需要订阅以及如何撤销您在创建订阅时进行的额外资源分配(如果有)的人。

但是在典型的 VueJS 应用程序中,您很少需要手动处理任何类型的清理(停止 DOM 修补、监视或计算等)。那是因为 VueJS 会自动处理它。 当组件被卸载时,在组件的设置方法中建立的反应效果将被自动处理(适当清理所需的任何内容)。这是怎么发生的?假设 VueJS 内部还存在一些其他魔法,可以将您的所有效果与相应组件的生命周期相关联。从技术上讲,正如 RFC 所说,魔法是 effectScope.

从概念上讲,每个组件创建一个 effectScope。在组件设置方法中定义的所有效果都将与该范围相关联。当组件销毁时,VueJS 会自动销毁作用域,它会清理关联的效果。

RFC 提议将 effectScope 变成 public api 这样人们就可以在不使用 VueJS 组件的情况下使用它。这是可能的,因为 Vue3 是用模块化构建的。您可以在不使用整个 VueJS 的情况下使用 Vue 的反应模块。但是如果没有底层的 effectScope,你就必须手动处理所有的 effects。