为何Mobx的computed计算属性被触发两次而非一次?
这是个很有意思的现象,咱们来一步步拆解背后的原因:
核心背景:Mobx批处理与React事件的集成
首先要明确一个关键点:Mobx和mobx-react集成时,React的合成事件(比如onClick)回调会被自动包裹在Mobx的batch函数中——这意味着回调内所有的observable修改,理论上会被批处理,直到回调结束后才统一通知依赖(computed、observer组件等)更新。
那为什么Case 2里computed触发了两次,既不是预期的一次(完全批处理)也不是三次(无批处理)?
关键原因:Mobx的懒计算与依赖追踪逻辑
Mobx的computed属性是懒计算的:只有当它被某个观察者(比如observer组件、autorun)读取时,才会重新计算值;否则只会被标记为"脏"状态,不会主动计算。
在Case 2的场景中,虽然React事件回调被batch包裹,但因为没有用@action修饰修改函数,Mobx的内部处理会出现这样的流程:
- 修改
firstName:标记fullName为脏。 - 修改
lastName:再次标记fullName为脏(但此时它已经是脏状态,无额外操作)。 - 修改
initial:第三次标记fullName为脏。
当batch结束后,Mobx会通知所有依赖fullName的观察者(也就是ProfileView组件)需要更新,此时:
- 第一次
Computed日志:Mobx在调度组件更新前,会提前尝试计算fullName的最新值(这是Mobx内部的优化逻辑,确保组件渲染时能拿到最新的缓存值)。 - 第二次
Computed日志:当ProfileView组件实际重新渲染时,会读取fullName的值——由于此时fullName的缓存已失效,所以会再次触发计算。
而Case 1中,@action修饰的函数会将所有修改合并为一个原子操作:Mobx只会在所有修改完成后,一次性标记fullName为脏,并且仅在组件渲染时触发一次computed计算,因此只会出现一次Computed日志。
额外验证小技巧
你可以在Case 2的changeFirstNameAndLastName函数末尾加一行console.log(newPerson.fullName),此时会发现Computed日志变成三次——因为在函数内部读取fullName时会触发一次计算,加上后续的两次,正好对应三次修改的触发时机,这也直接印证了computed的懒计算特性。
内容的提问来源于stack exchange,提问作者Pavan Gangireddy

