前端框架的未来:useSignal()

Signal是一种在前端框架中处理应用状态的方式,类似于React的useState,但返回getter和setter。Signal的响应式特性允许它跟踪状态的依赖,从而优化重渲染,减少不必要的计算。与React的useState相比,Signal更高效,因为它能精确地知道状态变化影响的位置,而useState可能导致整个组件树的重渲染。Signal在Vue、Preact、Solid和Qwik等框架中得到支持,展示出其在状态管理方面的优势。

Signal(信号)是一种存储应用状态的形式,类似于 React 中的 useState()。但是,有一些关键性差异使 Signal 更具优势。Vue、Preact、Solid 和 Qwik 等流行 JavaScript 框架都支持 Signal。

Signal 并不是最近才出现的,在此之前,它已经存在于 Knockout 等框架中。不过,在最近几年通过巧妙的编译器技巧和与 JSX 的深度集成极大地改进了它的开发者体验·,这使得它非常简洁并且使用起来很方便。

下面就来看看 Signal 都有哪些优势,为什么说它是前端框架的未来!

Signal 是什么?

Signal 和 State 之间的主要区别在于 Signal 返回一个 getter 和一个 setter,而非响应式系统返回一个值和一个 setter。

注意: 有些响应式系统同时返回一个 getter/setter,有些则返回两个单独的引用,但思想是一样的。

Signal 的优势

State 混淆了两个独立的概念:

  • StateReference:对状态的引用。

  • StateValue:存储在状态引用/存储中的实际值。

那为什么返回一个 getter 比返回一个值更好呢?因为通过返回 getter,可以将状态引用的传递与状态值的读取分开。

下面以这段 SolidJS 代码为例:

  • createSignal():分配 StateStorage 并将其初始化为 0。

  • getCount:可以传递的对状态的引用。

  • getCount():获取状态值。

上面解释了 Signal 和普通 State 的不同。那 Signal 到底有什么优势呢?Signal 是响应式的,这意味着它需要跟踪谁对状态感兴趣(订阅者),如果状态发生变化,则通知订阅者状态变化。

为了具有响应式,Signal 必须要收集谁对它的值感兴趣。它通过观察在什么情况下调用状态 getter 来获取此信息。通过从 getter 中获取值,告诉 Signal 该位置对该值感兴趣。如果值发生变化,则需要调用 getter 创建一个订阅。

这就是为什么传递状态 getter 而不是状态值很重要的原因。状态值的传递不会向 Signal 提供有关实际使用该值的位置的任何信息。这就是为什么区分状态引用和状态值在 Signal 中如此重要。

为了进行对比,这里是 Qwik 中的相同示例。请注意,(getter/setter) 已被替换为具有 .value 属性(表示 getter/setter)的单个对象。虽然语法不同,但内部的工作原理是一样的。

当单击按钮并增加值时,框架只需要将文本节点从 0 更新为 1。它可以这样做是因为在模板的初始渲染期间,Signal 已经知道 count.value 只能被文本节点访问。因此,它知道如果 count 的值发生变化,就只需要更新文本节点而无需更新其他地方。

useState() 的缺点

下面来看看在 React 中是如何使用 useState() 的以及它的缺点。

React 的 useState() 会返回一个状态值。这意味着 useState() 不知道组件或应用内部如何使用状态值。所以,一旦通过调用 setCount() 通知 React 状态更改,React 就不知道页面的哪一部分发生了更改,因此必须重新渲染整个组件,这在计算上是很昂贵的。

useRef() 不渲染

React 的 useRef() 类似于 useSignal(),但它不会导致页面重新渲染。下面的例子看起来与 useSignal() 非常相似,但它不起作用。

useRef() 的使用与 useSignal() 完全一样,用于传递对状态的引用而不是状态本身。但是,useRef() 缺少了订阅跟踪和通知。

在基于 Signal 的框架中,useSignal() 和 useRef() 是一样的。useSignal() 可以执行 useRef() 加上订阅跟踪的功能,这样就进一步简化了框架的 API。

内置的 useMemo()

Signal 很小需要进行记忆,因为它需要做的工作很少。

下面来看一个包含两个计数器和两个子组件的例子:

这里只会更新两个 Display 组件之一的文本节点。未更新的文本节点在初始渲染后将永远不会打印。

# 初始渲染输出
<Counter/>
<Display count={0}/>
<Display count={0}/>

# 单击时的渲染
(空白)

实际上,我们无法在 React 中实现相同的效果,因为至少会有一个组件需要重新渲染。那么下面就来看看如何在 React 中记忆组件以最小化重新渲染的次数。

但即使进行了记忆,React 也会产生多次重新渲染:

 

# 初始渲染输出
<Counter/>
<Display count={0}/>
<Display count={0}/>

# 单击时的渲染
<Counter/>
<Display count={1}/>

如果没有记忆,我们会看到:

# 初始渲染输出
<Counter/>
<Display count={0}/>
<Display count={0}/>

# 单击时的渲染
<Counter/>
<Display count={1}/>
<Display count={0}/>

这比 Signal 需要做的工作多得多。所以,这就是为什么 Signal 的工作就像把所有事情都记下来了,而不必由我们自己来记忆。

举个例子

下面就来看一个实现购物车的例子(React):

APP 组件中定义了购物车的状态 cart 以及两个子组件:

 

  • Main 组件:通过多层组件传递 setCart 函数,直到它到达购买按钮。

  • NavBar 组件:通过多层组件传递购物车状态,直到它到达渲染购物车的组件。

这里的问题就是每次点击购买按钮时,大部分组件树都必须重新渲染。这会导致类似于的输出:

# 点击购买按钮时
<App/>
<Main/>
<Product/>
<NavBar/>
<Cart/>

如果使用记忆,那么就可以避免 Main 组件重新渲染,而只有 NavBar 组件重新渲染:

# 点击购买按钮时
<App/>
<NavBar/>
<Cart/>

如果使用 Signal,输出是这样的:

# 点击购买按钮时
<Cart/>

这大大减少了需要执行的代码量。

总结

Signal 是在应用中存储状态的一种方式,类似于 React 中的 useState()。两者的关键区别在于,Signal 返回一个 getter 和一个 setter,而非响应式系统只返回一个值和一个 setter。

Signal 是响应式的,这意味着它需要跟踪谁对状态感兴趣并通知订阅者状态更改。这是通过观察调用状态 getter 的上下文来实现的,它创建了一个订阅。

相比之下,React 中的 useState() 仅返回状态值,这意味着它不知道如何使用状态值,并且必须重新渲染整个组件树以响应状态变化。

所以说 Signal 是前端框架的未来!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值