Signals大火!为何众多前端主流框架都要实现它?

话不多说,直接开始!
1.什么是反应式编程反应式编程是一种编程思想与方式,是为了简化并发编程而出现的 。与传统的处理方式相比,反应式编程能够基于数据流中的事件进行反应处理 。
例如:在 a+b=c 的场景,在传统编程方式下如果 a、b 发生变化,那么需要重新计算 a+b 来得到 c 值 。而反应式编程中,不需要重新计算 。a、b 的变化事件会触发 c 的值自动更新 。这种方式类似于在消息中间件中常见的发布/订阅模式 。由流发布事件,而代码逻辑作为订阅方基于事件进行处理,并且是异步执行的 。

Signals大火!为何众多前端主流框架都要实现它?

文章插图
 
反应式编程中,最基本的处理单元是事件流(事件流是不可变的,对流进行操作只会返回新的流)中的事件 。核心是基于事件流、无阻塞、异步的,使用反应式编程不需要编写底层的并发、并行代码 。并且由于其声明式编写代码的方式,使得异步代码易读且易维护 。
常用的反应式编程类库包括:Reactor、RxJAVA 2、Vert.x 以及 Ratpack 等等 。
2.可以自动化执行的逻辑都将自动执行当 React 被引入时,比其他任何库和框架都更加吸引开发者,它引入了一个非常有趣的概念称为单向数据绑定,或者更简单地说,作为虚拟 DOM 的一部分引入的单向数据流 。
它提供了一种全新的体验,即当数据状态发生变化时,开发人员不必考虑更新如何在 UI 中流动 。然而,随着越来越多的 hooks 被引入,有一些语法规则可以确保它们以最佳方式执行 。从本质上讲,与 React 的原始目的有偏差,即单向流或显式突变(explicit mutations) 。比如:
  • 自动正确填写依赖数组
  • 自动记住正确的值或回调以进行渲染优化
  • 有意识地避免 prop drilling
然而,如果无法有效、正确地避免以上情况,可能导致一些严重的性能问题,即一股脑的重新渲染 。这与仅编写组件来构建 UI 的初衷略有不同 。
props drilling 是数据以 props 的形式从 React 组件树中的一部分传递到另一部分, 只是传递的组件层级过深、而中间层组件并不需要这些 props,只是做一个向下转发, 这种情况就叫做 props drilling 。
信号(Signals)采用反应式编程原语来帮助消除复杂性,并通过将注意力转移到正确的事情上来帮助改善开发人员体验,而不必明确遵循一组语法规则来获得性能提升 。
3.什么是信号【Signals大火!为何众多前端主流框架都要实现它?】信号是反应式编程的关键原语(Primitive)之一 。从语法上讲,它们与 React 中的状态管理非常相似 。然而,信号的反应式能力赋予了它的诸多优势 。比如下面的例子:
const [state, setState] = useState(0);// state -> value// setState -> setterconst [signal, setSignal] = createSignal(0);// signal -> getter// setSignal -> setter看起来几乎相同,除了 useState 返回一个值而 useSignal 返回一个 getter 函数 。
信号在其概念中相当于一个值的框,当一个框中的值发生变化时,所有相关框中的值都会自动更新 。Signal 会重复该过程,直到更新所有框 。
乍一看,这非常类似于 useState 和 useEffect 的组合,但请注意信号是全局定义的 。这允许,当框中的值更改时,仅刷新 VirtualDOM 中依赖于它的那些组件 。
import { signal, computed } from "@preact/signals";const count = signal(0);// computedconst double = computed(() => count.value * 2);effect(() => console.log(double.value)));function Counter() {return (<button onClick={() => count.value++}>{count} x 2 = {double}</button>);}当然,这并不是 Signals 优势的全部 。在应用程序的整个生命周期中,信号引用不会改变,因此开发者不必担心多余的渲染 。computed 和 effect 函数都不需要依赖项列表而是会自动检测 。此外,如果依赖关系发生变化,但最终值保持不变,也不会刷新依赖关系 。
本质上,Signals 是一个用纯 JavaScript 编写的库, 如果开发者在 JSX 中使用 signal,而不是 signal.value,它将被视为一个单独的组件 。这意味着如果它的值发生变化,不会渲染整个父组件,只会刷新一小段文本 。
// 在此示例中,整个 Counter 组件将在计数更改时重新渲染const count = signal(0);function Counter() {return (<><SomeOtherComponent />Value: {count.value}</>);}// 在此示例中,只有计数值会在计数更改时重新渲染const count = signal(0);function Counter() {return (<><SomeOtherComponent />Value: {count}</>);}


推荐阅读