谈谈我对 Reacitive 方法的理解

本文我想和大家分享一下我对当前 Reactivity 方法和现状的理解。我并不是说我的观点就是对的,但我认为,正是通过分享自己的观点,我们才能对行业中的事物达成共识,我希望这些来之不易的见解能够对其他人有所帮助,并补充他们理解中缺失的部分。

reacitve 三剑客

我认为到目前为止,我们在行业中看到的 reacitive 方法有三种:

  1. 基于 value:也就是脏检查,应用的框架有 Angular, React, Svelte;
  2. 基于 observable : 应用的框架有 Angular with RxJS, Svelte;
  3. 基于 singnal:应用的框架有 Angular with signals, Qwik, React with MobX, Solid, Vue

接下来我来谈谈这三种方法:

基于 value

基于 value 的系统依赖于将状态作为简单值存储在“不可观察”引用中。

当我 说“observable” 时,我并不是指的是像 RxJS 这样的可观察对象。我指的是“可观察”这个词的常用用法,比如知道它什么时候发生了变化。“不可观察”意味着当值发生变化时,没有办法及时知道具体的实例。下面我给出三个例子:

  • React
function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
  • Angular
import { Component } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    

Counter: {{ count }}

`
, }) export class CounterComponent { count: number = 0; increment() { this.count++; } }
  • Svelte
<script>
  let count = 0;

  function increment() {
    count += 1;
  }
</script>

<div>
  <h1>Counter: {count}</h1>
  <button on:click={increment}>Increment</button>
</div>

在上面的每种情况下,状态都作为一个值存储在变量中。但关键是它是一个不可观察的值,以一种不允许框架知道(观察)值何时变化的方式存储在 JavaScript 中。

由于该值的存储方式不允许框架观察到变化,因此每个框架都需要一种方法来检测这些值何时发生变化,并将组件标记为脏组件。

一旦标记为 dirty,就会重新运行组件,以便框架可以重新读取/重新创建值,从而检测哪些部分发生了更改,并将更改反映到 DOM。

脏检查是基于 value 的系统所能采用的唯一策略。它将最后一个已知值与当前值进行比较

那怎么知道什么时候运行脏检查算法呢?通常不同的框架方式不同:

  • Angular: 隐式依赖 zone.js 来检测状态何时可能发生了变化。(因为它依赖于通过zone.js 的隐式检测,所以运行变更检测的频率比严格必要的要高。)
  • React: 显式依赖于开发人员调用 setState()
  • Svelte: 自动生成 setState() 调用

基于 Observable

Observable 对象是随时间变化的值。Observable 对象允许框架在值发生变化时及时知道具体的实例,因为将新值推送到 Observable 对象中需要特定的 API 来充当保护。

可观察对象是解决细颗粒 Reacitive 问题的明显方法。但是,因为 observable 需要显式调用 .subscribe() 和相应的调用 .unsubscribe(),导致开发体验不好 。可观察对象也不能保证同步无故障的交付,UI 倾向于同步更新。下面我们给出代码示例:

  • Angular
import { Component } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';

@Component({
  selector: 'app-counter',
  template: `
    

Counter: {{ count$ | async }}

`
, }) export class CounterComponent { private countSubject = new BehaviorSubject<number>(0); count$: Observable<number> = this.countSubject.asObservable(); increment() { this.countSubject.next(this.countSubject.value + 1); } }
  • Svelte
<script>
  import { writable } from 'svelte/store';

  const count = writable(0);

  function increment() {
    // Update the count by incrementing it
    count.update(n => n + 1);
  }
</script>

<div>
  <h1>Counter: {$count}</h1>
  <button on:click={increment}>Increment</button>
</div>

Svelte: 有趣的是,它有两个具有不同心智模型和语法的 Reacitive 系统。这是因为基于value 的模型只在 .svelte 文件中工作,所以将代码移出 .svelte 文件需要一些其他的 Reacitive 原语(Stores)。

我相信每个框架都应该有一个可以处理所有用例的单一 Reacitive 模型,而不是基于用例的不同 Reacitive 系统的组合。

基于 Signal

Signal 就像可观察对象的同步表兄弟没有订阅/取消订阅。我相信这是一个重大的编码改进,我也相信 Signal 是未来。

Signal 的实现并不明显,这就是为什么行业花了这么长时间才走到这一步。Signal 需要与底层框架紧密耦合,以获得最佳的编码体验和性能。

为了获得最好的结果,需要协调框架渲染和可观察对象更新。下面给出几个示例:

  • Qwik
export const Counter = component$(() => {
  const count = useSignal(123);
  return (
    <button onClick$={() => count.value++}>
      {count.value}
    </button>
  );
});

  • SolidJS
export const Counter = (() => {
  const [count, setCount] = createSignal(123);
  return (
    <button onClick={() => setCount(count() + 1)}>
      {count()}
    </button>
  );
});

  • Vue
<template>
  <section>
    <h1>Count: {{ count }}</h1>
    <button @click="incrementCount">+1</button>
  </section>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);
function incrementCount() {
  count.value++;
}
</script>

Angular 正在研究 Signal ,但它们仍然需要 Signal 和模板的集成。

最后,总结一下我的观点。

可观察对象太复杂了,不太适合。因为只有 BehaviorSubject 可观察对象才能真正与 UI 一起工作。

在基于 Value 的系统中,性能又是极其消耗的。虽然值的变化不会破坏应用程序,只是当有一天你觉它太慢了的时候,并且当你想要进行优化它时,就会发现没有“明显”的东西需要修复。

对于基于 Signal 的系统,对于开发者,最初的理解门槛会稍微高一些,并且开发者很有可能从 Reacitive 悬崖上掉下来。因为如果你对 Signal 的反应错误,应用程序就会崩溃。但是解决问题的办法也会很明显。

其次,当一旦你开始优化基于 Value 的系统的时候,你就会开始接触到基于 Signal 的世界,在那里你可能会像处理 Signal 一样失去 Reacitive。本质上,基于 Value 的“优化”API是“低于标准的 Signal 的”。

这也是我喜欢 Signal 的第二个原因。Signal 开启了一种很酷的编码方式,它允许你可视化系统的响应式并调试它。

好啦,以上就是我的理解,希望对你有帮助!

你可能感兴趣的:(javascript,javascript,angular.js,前端)