我曾经谈到我认为signals
是Web
框架的未来。自那以后,我很高兴地欢迎Angular
加入了Signals俱乐部。
令我惊讶的是,安德鲁·克拉克(Andrew Clark)在一条推文中写道,他认为React Forget
比Signals
是一个更好的方法。
React优化编译器,又名React Forget
React Forget
是一个实验性项目,旨在通过自动生成等效的useMemo
和useCallback
调用来改善React
应用程序的渲染性能,以最小化重新渲染的成本,同时保留React
的编程模型。
Memoization
与signals
对于大多数情况来说,signals
不需要记忆。 React Forget
自动在所有地方插入记忆,所以我可以理解为什么有人会认为 Memoization
与signals
是相同的。
让我向你展示 Memoization
方法失败而signals
卓越的地方。
示例
让我们构建一个经典的单页应用程序,包含一个购买按钮和一个购物车。选择此示例是因为它展示了一个现实世界的场景,其中状态、变化和渲染被分离到不同的组件中:
- 声明状态的地方(购物车内容)
- 变异状态的地方(购买按钮)
- 渲染状态的地方(购物车UI)
使用Memoization和prop drilling的React
要理解的关键是,我们需要将状态(以及setter
)传递到需要它的组件中。
在我们的例子中,这是ShoppingCart
和BuyButton
组件。这意味着我们必须通过许多层组件进行prop drilling
才能到达那里(上下文是另一种方法,但结果相同)。
现在让我们看看当我们尝试改变购物车时会发生什么:
BuyButton
有一个setCart setter
,它使用购物车的新状态调用。- 调用
setCart
会使App
组件失效。 React从App
组件开始重新渲染。通常情况下,这会导致大多数子组件重新渲染,但让我们假设React Forget
已经将Memoization
最大化。在这种情况下,React
仍然需要渲染App
和ShoppingCart
之间的所有组件。在我们的简单情况中,只有Header
和User
,但在真实的应用程序中会有更多。
即使React Forget
编译器Memoization
了一切,它仍然无法避免的事实是,当状态发生变化时,它必须从状态存储的地方通过prop drilling
一直传递到需要的地方。
重新渲染仅用于prop drilling
的中间组件是一种浪费。
signals
现在让我们看看signals
。数据流图与以前完全相同,只是现在我们不是传递状态,而是通过许多层组件传递信号到ShoppingCart
和BuyButton
。
现在让我们看看当我们尝试改变购物车时会发生什么。
BuyButton
有一个setCart
信号,它使用购物车的新状态调用。- 信号通知
ShoppingCart
更新购物车的状态。
就是这样。
请注意,即使状态是在App
组件中声明的,但App
并不是重新渲染过程的一部分,而且所有通过prop drilling
将其传递给ShoppingCart
的组件也都不是。
这就是signals
的强大之处所在。
signals > useMemo()
这就是我们说,通常情况下,signals
不需要useMemo()
的原因。signals
使您能够仅对需要更新的UI
执行精细的更新,从而跳过所有中间部分。
useMemo()
无法跳过组件层次。在最好的情况下,useMemo()
可以修剪组件树,使得需要访问的分支更少,但更新仍然需要我们通过这些分支进行。
结论
signals
和Memoization
可能是等效的,但实际上,signals
要更高效得多,因为它们不受组件渲染树的限制。
它们存在于与组件树独立的平面上。
事实上,signals
不需要Memoization
,但在 React
中的Memoization
无法与signals
的精确性相匹配。