前端框架的未来:useSignal()
【文末包邮送书】
Signal(信号)是一种存储应用状态的形式,类似于 React 中的 useState()
。但是,有一些关键性差异使 Signal 更具优势。Vue、Preact、Solid 和 Qwik 等流行 JavaScript 框架都支持 Signal。
下面就来看看 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)的单个对象。虽然语法不同,但内部的工作原理是一样的。
count.value
只能被文本节点访问。因此,它知道如果 count
的值发生变化,就只需要更新文本节点而无需更新其他地方。
useState() 的缺点
下面来看看在 React 中是如何使用 useState()
的以及它的缺点。
useState()
会返回一个状态值。这意味着 useState()
不知道组件或应用内部如何使用状态值。所以,一旦通过调用 setCount()
通知 React 状态更改,React 就不知道页面的哪一部分发生了更改,因此必须重新渲染整个组件,这在计算上是很昂贵的。
useRef() 不渲染
React 的 useRef()
类似于 useSignal()
,但它不会导致页面重新渲染。下面的例子看起来与 useSignal()
非常相似,但它不起作用。
useRef()
的使用与 useSignal()
完全一样,用于传递对状态的引用而不是状态本身。但是,useRef()
缺少了订阅跟踪和通知。
在基于 Signal 的框架中,useSignal()
和 useRef()
是一样的。useSignal()
可以执行 useRef()
加上订阅跟踪的功能,这样就进一步简化了框架的 API。
内置的 useMemo()
Signal 很小需要进行记忆,因为它需要做的工作很少。
下面来看一个包含两个计数器和两个子组件的例子:
# 初始渲染输出
<Counter/>
<Display count={0}/>
<Display count={0}/>
# 单击时的渲染
(空白)
实际上,我们无法在 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):
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 是前端框架的未来!
包邮送书
前端充电宝 x 清华大学出版社 联合为大家送福利,免费抽送 6 本《Vue.js+Django高性能全栈论道》,包邮!活动规则如下:
活动一:在本文任意留言,2 月 22 日 17:00 将在评论区随机抽取 4 位幸运粉丝分别送一本; 活动二:2 月 20 日在朋友圈点赞,第 18 名和 38 名将分别送一本(微信见文末)。
本书主要内容包括网络编程与异步并发的基础,软件工程的设计模式在前端技术中的演进,从 Vue.js 的核心开发指南到 Webpack 编译打包的优化经验分享,Web/Service Workers 与 WebSocket 为 Vue.js 实现多线程离线加速,揭秘Vue.js全方位异步惰性加载优化,Django、PyPy、WSGI 和Gevent 的全套异步方案实战,Asyncio、gRPC、Channels 与 Django 的分布式应用实战,Python Agent 技术分享。
长按添加微信,参与活动~
往期推荐: