Talk about my understanding of the Reactive method

In this article I would like to share with you my understanding of the current Reactivity methods and status quo. I'm not saying that my views are right, but I think it's by sharing my views that we can come to a common understanding of things in the industry, and I hope these hard-earned insights can be helpful to others and Fill in the missing pieces of their understanding.

reacitve The Three Musketeers

I think there are three approaches to reacitive that we have seen in the industry so far:

  1. Based on value: that is, dirty checking, the application frameworks include Angular, React, and Svelte;
  2. Based on observable: application frameworks include Angular with RxJS, Svelte;
  3. Based on singnal: application frameworks include Angular with signals, Qwik, React with MobX, Solid, Vue

Next let me talk about these three methods:

based on value

Value-based systems rely on storing state as simple values ​​in "unobservable" references.

When I say "observable", I don't mean observable objects like RxJS. I'm referring to the common use of the word "observable", as in knowing when it has changed. "Unobservable" means that there is no way to know the specific instance in time when the value changes. Below I give three examples:

  • 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: `
    <h1>Counter: {
     
     { count }}</h1>
    <button (click)="increment()">Increment</button>
  `,
})
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>

In each case above, the state is stored as a value in a variable. But the point is that it is an unobservable value, stored in JavaScript in a way that does not allow the framework to know (observe) when the value changes.

Since the way the value is stored does not allow the framework to observe changes, every framework needs a way to detect when these values ​​change and mark the component as Dirty components.

Once marked dirty, the component is re-run so that the framework can re-read/re-create the values, detect which parts have changed, and reflect the changes to the DOM.

Dirty checking is the only strategy that value-based systems can adopt. It compares the last known value to the current value.

So how do you know when to run the dirty checking algorithm? Usually different frameworks do it differently:

  • Angular: Implicitly relies on zone.js to detect when state may have changed. (Because it relies on implicit detection via zone.js, run change detection more often than strictly necessary.)
  • React: Explicitly dependent on developer calling setState().
  • Svelte: Automatically generated setState() for tuning.

Based on Observable

Observable objects are values ​​that change over time. Observable objects allow the framework to know when a value changes in a specific instance since pushing new values ​​into an Observable object requires a specific API to act as a guard.

Observables are the obvious solution to fine-grained Reactive problems. However, because observable needs to explicitly call .subscribe() and the corresponding call .unsubscribe(), the development experience is not good. Observables also don’t guarantee synchronous, glitch-free delivery, and the UI tends to update synchronously. Below we give a code example:

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

@Component({
    
    
  selector: 'app-counter',
  template: `
    <h1>Counter: {
     
     { count$ | async }}</h1>
    <button (click)="increment()">Increment</button>
  `,
})
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: Interestingly, it has two Reactive systems with different mental models and syntax. This is because the value-based model only works in .svelte files, so moving the code out of .svelte files requires some additional Reacitive primitives (Stores).

I believe that every framework should have a single Reacitive model that can handle all use cases, rather than a combination of different Reacitive systems based on use cases.

Based on Signal

Signal is like the synchronous cousin of Observable, without subscription/unsubscription a>. I believe this is a major coding improvement, and I also believe Signal is the future.

The implementation of Signal is not obvious, which is why it took the industry so long to get to this point. Signal needs to be tightly coupled with the underlying framework for the best coding experience and performance.

For best results, frame rendering and observable updates need to be coordinated. A few examples are given below:

  • Quick
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>
  );
});

  • View
<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 is working on Signal but they still need integration of Signal and templates.

Finally, let me summarize my point of view.

Observables are too complex and not a good fit. Because only BehaviorSubject observables can really work with UI.

In a Value-based system, performance is extremely expensive. While changing the values ​​won't break the application, it's just that one day you decide it's too slow and you want to optimize it and you realize there's nothing "obvious" to fix.

For Signal-based systems, the initial barrier to understanding for developers will be slightly higher, and developers will most likely fall off the Reactive cliff. Because if you respond to Signal wrongly, the app will crash. But the solution to the problem will also be obvious.

Secondly, once you start optimizing a Value-based system, you start to get exposed to the Signal-based world, where you can lose reacitive as much as you do with Signal. Essentially, the "optimized" API based on Value is "subpar Signal ".

This is also the second reason why I like Signal. Signal opens up a cool way of coding that allows you to visualize the responsiveness of your system and debug it.

Okay, the above is my understanding, I hope it helps you!

Guess you like

Origin blog.csdn.net/ImagineCode/article/details/134107294