编程手搓工艺技能 三种反应式算法一次讲透:推/拉/推拉混合 #架构师资料教程 #Reactive编程指南 #前端编程与架构 #ReactJS教程 2026-07-28 3K banq
写代码的人最怕改一个数据然后整个世界都卡住:这个电子表格里有三百万个公式,改一个数字电脑就死机了,谁受得了?

本文介绍了三种反应式编程算法:推、拉、推拉,并解释了它们在电子表格和大型软件中的作用。

--91likeyou---

我们不用背概念,直接看它们怎么在表格里打架。

单元格的祖宗十八代都等着你改数据

打开一个电子表格,你看见的是格子。程序看见的是一座山。

这座山分三层。最顶上是你手动填的数字,叫输入单元格。最底下是最终结果,叫输出单元格。中间夹着密密麻麻的中间计算单元格,每个都靠上面的数据活着。

改一个顶层的数字,就像在山顶踢了块石头。这块石头往下滚,砸中第二层的石头,第二层再砸第三层。一直到最底下。

每个被砸中的格子都得重新算一遍自己的值。这就是反应式编程要干的事。

但这里面有四个麻烦:
第一个,每个格子只能算一次,算了又扔那是傻子。
第二个,只算那些真的被影响到的格子,没被砸中的别碰。
第三个,所有格子必须同时变完,不能出现中间状态被人看见。
第四个,格子能随时加新的亲戚或者断掉旧的亲戚关系。

这四个要求凑一块,写程序的人就头疼了。

第一种推模式特热情,改个数字恨不得告诉全世界

推模式特别简单粗暴:一个格子的值变了,它就挨个通知所有依赖它的格子。

通知的方式就是喊一嗓子:哥们我变了,你也赶紧变。收到通知的格子重新算完自己,又去通知下一层。一路推下去,直到最底下。

这种模式的好处是精准。
谁依赖我,我就通知谁,没有多余的废话。电子表格和图形界面都用这套,因为每次改动影响的范围通常不大。

但问题出在效率上。
想象三个格子A、B、C。A变了,B和C都依赖A。B还额外依赖C。在推模式下,A先通知B和C。C算完了又回头通知B。B收到两次通知,第一次白算了,第二次才算对。更惨的是D,它同时依赖B和C,能收到三次通知,前两次全浪费。

这就好比你在群里发了个消息,一群人开始讨论,讨论到一半突然有人发现前面说错了,又倒回去重说。整个群乱成一锅粥。

解决这个问题需要知道全部格子的依赖关系,然后排个顺序。先算谁后算谁得提前定好。但推模式的设计初衷就是每个格子只知道自己依赖谁,不知道全局长啥样。这矛盾了。

更烦人的是中间状态泄漏。A变完了,B还没变完,这时候如果有别的代码跑过来读B的值,读到的就是过期的。像两个格子存国家代码和国家全名,代码更新了但名字没跟上,中间状态被人瞅见了,就出大乱子。

为了堵这个漏洞,要么把所有观察代码都变成依赖节点排在最后,要么干脆不允许在更新过程中读数据。但写代码的人总有办法绕过去,最终还是要靠自觉。

第二种拉模式特别懒,你不问它就不动

拉模式正好反过来。推是爸爸主动给钱,拉是儿子上门要钱。

在拉模式下,输出格子想知道自己该是多少,就去问它的上一层。上一层也不知道,就继续往上问。一路问到最顶上的输入格子,拿到数值,再一层层传回来。

这其实就是函数调用。你调用一个函数,函数里调用更多函数,最后返回结果。嵌套多少层都行,依赖自动就解开了。

但光这样还不算反应式。还得有个触发器,让这套机制在数据变化时重新跑一遍。

最简单粗暴的办法是每次数据变了,把所有格子全部重算一遍。从头到尾,一个不落。依赖关系通过函数调用自动处理,调用顺序天然就是对的。

这种办法的好处是绝对没有中间状态问题。因为所有的计算都在一次递归里完成,中间不会插入别的操作。在JavaScript这种单线程环境里,这就是铁打的保障。

动态依赖也白送。条件公式里,如果条件不满足就不调用那个函数,依赖自然就不存在。不需要维护什么依赖列表,现用现问就行。

但缺点也很致命。如果一万个格子里只改了一个输入,其他九千九百九十九个格子完全没变,拉模式照样全部重算一遍。浪费得让人心疼。

加上缓存能解决一点。算过的值存起来,下次直接用。但缓存失效是计算机科学里最难的问题之一。改了一个输入,哪些缓存该清掉?搞不好就清了不该清的,或者该清的没清掉。

更头疼的是,拉模式从头到尾不知道哪个格子最终会变。它只能全算一遍。想只算变化的格子?抱歉,信息不够。

React框架用了拉模式,但加了个限制:只更新某个组件及其子树。这算是个折中方案,但不是通用解法。

第三种推拉结合的模式先标记再动手,聪明得刚刚好

推拉结合的模式聪明在分两步走。第一步只标记,第二步才真算。

每个格子上挂一个小旗子,叫脏标记。初始状态所有旗子都是干净的,表示数值有效。

第一步是推。
修改了输入格子,就从它开始往下走。走到一个格子,先看它的旗子是不是已经脏了。脏了就不管,跳过。没脏就给它插上脏旗子。如果这个格子是输出格子,就记到待更新清单里。然后继续往下走,直到走完所有受影响的路。

这一步只插旗子,一个格子只插一次。顺序无所谓,先走哪条路都一样。因为插旗子这个动作没有副作用,不影响结果。

第二步是拉。
拿着待更新清单,挨个处理。处理某个输出格子时,它向上问依赖。如果依赖的旗子是干净的,直接拿数值。如果依赖的旗子是脏的,就递归地去算那个依赖的值,算完把旗子擦干净,存下新数值。

所有格子算完,所有旗子都干净了,所有输出格子也都是最新的了。

这套组合拳把推和拉的毛病都治了。效率上,推的时候每个脏格子只访问一次,拉的时候也只算一次。精细化上,只算那些被插了旗子的,别的碰都不碰。无中间状态上,因为所有真正计算都在拉这一步完成,和纯拉模式一样安全。动态依赖上,拉模式天然支持动态,推的标记阶段也不需要全局顺序,每个格子只记邻居就行。

缺点也有一个。整个拉的过程得在一个时间片里完成,中间不能插新的数据修改。如果计算特别重,拖得太久,就得拆成状态机慢慢跑。但那样复杂度就上去了。

三种套路各有各的命

推模式适合事件驱动:用户点了个按钮,触发的逻辑链通常不深,乱序重算的问题不突出。但得小心中间状态被偷看。

拉模式适合依赖关系复杂但数据量不大的场景:React那种组件树,每个组件独立更新,代价可以接受。但数据量一大就跪。

推拉结合模式是万金油:大型电子表格引擎、复杂GUI框架,都用这套。复杂度可控,性能也不错。但得保证一次更新能在一个循环里完成。

写程序选哪个,看你的数据山有多高,石头滚下来砸中的地方多不多,以及你怕不怕中间状态被人看见。没有完美的算法,只有合适的取舍。

如果你老板让你做个实时更新的表格,先别急着写代码。问问自己:数据量大不大?改动频繁不频繁?中间状态能不能忍?答案凑一块,自然就知道该推还是该拉还是该混合着来了。

 

🔥 热词:#推拉效应 化学 · #推拉法是什么意思 · #推拉法则 · #推拉效应什么意思 · #推拉现象是什么现象 · #什么是推-拉混合式供应链? · #什么是推拉理论? · #什么叫推式什么叫拉式