![]()
2010年Knockout.js把响应式编程搬进浏览器时,没人想到14年后这玩意儿会成为Solid、Vue、Preact的底层标配。但有个问题一直没人讲清楚:Signals到底怎么做到"改一个数,整棵树跟着动"的?
我翻完Solid和Vue的源码实现,发现核心是一套反直觉的推-拉混合算法——它既不像事件订阅那样全推,也不像懒加载那样全拉,而是两头占便宜。
先建一个"规则世界"
想象你在Excel里写公式:A1=10,B1=A1*2,C1=B1+5。改A1的时候,B1和C1自动重算——这就是响应式的本质。
代码里也一样。你定义一条规则:y永远等于2*x。一旦x变了,y自动跟上,不需要手动调用setState。z再依赖y,形成链条。程序跑起来后,这些规则在时间轴上持续生效,你只管观察结果。
这种模型1970年代就有了,叫响应式编程(Reactive Programming)。2010年Knockout.js第一次把它塞进浏览器,2012年RxJS跟进,但两者都太重。Signals是它们的直系后代,只是砍掉了90%的API表面,留下最锋利的内核。
一个Signal长这样:
get时收集依赖,set时通知订阅。看起来简单,但魔鬼在"通知"这一步——直接全量推送?性能爆炸。完全懒加载?UI stale。推-拉算法就是来解决这个矛盾的。
推-拉:两头讨好的艺术
纯推送模型(Push)的问题是:x变了,马上通知y,y通知z,z通知w……哪怕w最终没被用到,计算也跑完了。像React 18之前的同步渲染,一个state变化能触发整棵树遍历。
纯拉取模型(Pull)的问题是:等到真正读取w时才去算,中间链条可能已过期。你拿到一个值,不确定它是不是最新的,得自己检查版本号。
Signals的解法是分阶段:
第一阶段推标记(Push Mark):x变了,不立即重算y,只给y打个"脏"标签,然后停住。y给z打标签,z给w打标签,整条链只传播一个布尔值,成本极低。
第二阶段拉取值(Pull Value):等到UI真正要渲染w时,才从w往回拉——w发现z脏了,让z重算;z发现y脏了,让y重算;y发现x确实变了,读取新值。没有中间商赚差价,没有无效计算。
Solid的作者Ryan Carniato把这叫做"细粒度响应式"。Vue 3.4的Vapor模式、Preact Signals都用了同一套思路,只是实现细节不同。
源码里的脏检查博弈
看Solid的实现:每个Signal和Computation(计算属性)都有version和state两个字段。state分0(干净)、1(检查中)、2(脏)。
设值时,Signal的version++,所有订阅者的state设为2(脏)。这一步是纯推送,只改状态位,不做计算。
取值时,如果state===2,执行重算;如果state===1,说明有循环依赖,直接报错。重算前先把state设为1(检查中),算完改回0(干净)。
Vue的响应式系统更复杂一点,因为还要处理模板编译优化。但核心逻辑一致:组件渲染函数本身也是一个Computation,依赖的响应式变量变了,组件标记为待更新,等到nextTick统一拉取。
这里有个反直觉的点:同一个Computation可能被多次标记为脏,但只执行一次。比如x和y同时变化,都通知了z。z第一次被拉取时重算,之后即使再有通知,version对不上就直接返回缓存。这是推-拉模型能压到O(1)更新的关键。
为什么现在才流行?
2019年Svelte 3发布时,Rich Harris说"虚拟DOM是纯粹的开销",但当时没人信。2021年Solid 1.0出来,用Signals跑出了比Svelte还快的benchmark,社区才开始认真看。
Vue 3.0 2020年发布,Composition API里埋了ref和reactive,但官方一直没提Signals这个词。直到2023年Vue 3.3的defineModel、3.4的Vapor模式,才把推-拉算法推到台前。Preact 2023年把Signals做成独立包,React团队虽然嘴上说"我们不需要",但useSignal的RFC已经有人在写了。
一个有趣的对照:React的useMemo也是"懒计算",但依赖数组要手动写,且每次渲染都要比较。Signals的依赖是运行时自动收集的,且更新粒度细到单个变量,不是整个组件。
这不是说Signals要取代所有方案。React的并发特性、时间切片,Signals很难直接复刻。但在"状态驱动视图"这个赛道上,推-拉算法确实把性能天花板抬高了整整一档。
我最后好奇的是:如果2010年Knockout.js就用了这套算法,前端框架史会不会被改写?还是说,只有等到硬件性能过剩、开发者开始挑剔每一毫秒时,这种"过度优化"才有市场?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.