DEV Community

题叶
题叶

Posted on

关于 Signals 得一些消息整理

对应视频 https://www.bilibili.com/video/BV1LY411k7Gp/?spm_id_from=333.999.0.0

话题

Twitter 上一轮一轮发生的话题, 我这边留的一些内容:


据说缘起的文章是这一篇,

其中主要的观点, 还是 Signals 能提供更精准的更新, 相比 React 性能更高. 而 React 即便写上各种 memo 变得啰嗦, 重复计算 virtual DOM 的行为还是很多.


什么是 Signals

我这边理解, Signals 在 FP 这边有自己的说法, 但是这边讨论的是前端基于类似 observable data 实现的结构,

  • FP: 通过一个结构(比如惰性的序列)来模拟状态的变化
  • 前端框架: reactive graph of observable data, 响应式更新的有向无环图

后面也看到有更精准的一些描述:

这个描述比较有意思, 也翻译过来看看:

信号表示连续的时间变化量,如电压、音频信号或当前的鼠标坐标。流(事件流)表示一连串的离散事件,如按键、网络数据包或金融交易。

关键的区别在于背压策略:信号是典型的懒惰,它们在采样之前不会计算或做工作,而且只有最新的值才是相关的(没有人关心鼠标刚才在哪里,当时没有人在看)。流是急切的,你不能跳过一个键盘事件或金融交易,即使管道是备份的--相反,你必须告诉上游放慢,以便你能赶上。事件流的好处是保证你能看到每一个事件,这意味着流适合于驱动副作用的序列(键盘事件->网络请求->数据库事务)。

信号很适合渲染,因为你只想以60fps的速度进行渲染(即使鼠标的更新速度更快,它也是如此)。渲染(比如说dom效果)确实是有效果的,但不是以离散的方式;dom是一种资源,它有一个mount/unmount对象的生命周期,由于这种对称性,它很适合渲染,而孤立的效果(没有相应的撤销操作)则很不适合信号,因为背压会丢掉事件,破坏系统状态。

你也可以使用流来进行渲染,但这是不理想的,而且可能会有很大的影响。如果你的应用程序被突发的事件噎住了,你想跳过前面,渲染最终的状态,而不必费心去渲染所有中间的历史状态。信号懒惰是实现这种 "工作跳过 "的原因;一个流必须依次处理每一个事件。

具体到比如 Solidjs 里边的 Signals, 它的实现, 按照前面那个文章说的, getter 里面会去做数据的监听, setter 里边会触发监听的数据的更新, 整个形成一个 reactive updating 的过程.


ryansolid 的整理

Solidjs 作者的直播录像 https://www.twitch.tv/videos/1754658430

还有两篇文章

这个整理得非常好, 建议看一遍(比我这个梳理得详细). 而且他是强烈鼓吹 Signals 方案的, 非常上心.


放大的视野(前端方案)

  • Vue React Solid Qwik
  • Svelte
  • ReScript, ClojureScript, PureScript

我的看法, 从 FP 语言的角度, 会觉得 React 非常自然. 比如"引用透明"的概念, 一块代码(除非是宏), 正常应该在各种位置表达的结果都是一致的, 作为变量, 作为表达式调用, 不应该收到上下文而总是改变(比如在不在 JSX 里, 行为不一致). FP 用户很习惯用值, 因为值是一致的.

而 Signals 在 Solidjs 这样的方案里的实现, 明显是带有副作用的, 而且为了性能, 有些代码是值执行一遍的, 这个在代码行为的一致性上就打折扣了. FP 用户习惯会把这当做 bug 的来源. 因此写得啰嗦反而能容忍一些.

其他的 Twitter 也多次提到了, React 当初设计, 就是优先考虑的 Consistency, 考虑状态的一致性, 性能没有放在第一位.

我认为切换到 FP 语言, 立场和视角是跟只注重性能和简短不同的. 当然也意味着很多人的视角也想要性能和简短清晰,


增量计算

问题的核心是"增量计算". 不过有这个笼统地概念, 没有具体说前端的方案.

https://en.wikipedia.org/wiki/Incremental_computing

当然 Signals 这样构造出计算的结构, 也是在一定程度上给增量计算提供信息. 这些方案, 我印象里 PureScript ClojureScript 社区也是有人尝试的, 记得比较模糊, 没法给出好的例子. 但是在 FP 语言里, 这种很容易带上 Monad 或者 Macro, 并不像 Svelte 这样简单粗暴.


放大的视野(跨语言)

  • Dioxus
  • Flutter
  • WebAssembly

我想说 Signals 提出的这个方案同时也是针对 DOM 这个场景. 如果是换语言, 比如换 Rust, 我们能提供一些其他的方案, 比如换渲染引擎, 比如 Flutter, 那么渲染的时候优化性能针对的是 Canvas 级别的优化, 并不是 Signals 到了, 问题直接解决了.


放大的视野(超越 UI)

再考虑更远的问题, 界面从 WebGL 开始渲染的话怎么办? 有一个 https://makepad.dev/ 作为例子, 性能是可以非常高的, 但是实现门槛也非常高. 这种体系之下 Signals 是不是足够. 不过这个问题难度挺大的, 我也不认为 React 的路径就能好好回答了.

从全局来说, 我们要优化从数据库到浏览器到屏幕像素, 完整链路的渲染, 中间还会涉及到网络的因素, Signals 方案肯定是不够的. 中间也有大量的需要优化的空间.


干活的工具

  • Signals 方案逐渐完善
  • Memoization 方案逐渐完善
  • 调试工具

我也只能否定"Signals 是未来的终极方案"这个说法, 讨论当中提出的 React 的问题也确实存在, React 需要语言或者编译器更深层的改造来加强. 但背后也有很多理念的不同, React 社区很多人也提到, 这是大概十年前就讨论过的, 先要一致性, 还是先要性能?

我自己的观点, 这些方案, 很大程度依赖工具, 两个方案都说得通, 实际使用我们关注的是遇到问题怎么能快速定位, 快速解决, 这当中工具的作用非常大.

如果工具其实一般般, 我个人是很倾向于 FP 思路, 然后主要依赖 memoization 来优化的, 这样代码更容易分析. 不危险的地方, 我还能直接上 Macro 简化. 我也觉得这个方案更可靠.

Signals 实现得好的话, 我相信性能天花板是可以比 FP 方案更高的, 而且暴力一点, 写代码相当于大量使用优化过报错的 Macro, 可以做到非常精简, 特定场景用起来会非常舒服, 还不用学 FP 语言那么多的概念, 门槛很低, 也能很好.

Top comments (1)

Collapse
 
spades profile image
Spades

Great article