如何避免React在MobX可观察数组添加元素时重复渲染(Hooks版)
首先,我得帮你理清两个核心点:为什么你的组件会多次渲染,以及怎么解决这个问题。
为什么会触发多次渲染?
你当前的代码里,addMemo方法中每次调用this.messages = this.messages.concat(memos)都会创建一个新的数组引用,MobX会追踪这个observable数组的引用变化,所以每次赋值都会触发useObserver包裹的组件重新渲染。另外你提到组件渲染了10次,大概率是因为memo.letsDance()方法里修改了Memo实例上的observable属性,而这些属性被App组件间接追踪到了——虽然addMemo被标记为action,但如果letsDance没被标记为action,MobX的批量更新就没生效,导致每一次letsDance都触发一次渲染。
解决方案
1. 使用MobX observable数组的内置方法(推荐)
MobX的observable数组自带增强版的数组方法(比如push、unshift等),这些方法会直接修改原数组,不会创建新引用,还会自动批量处理更新,避免不必要的渲染。把你的代码改成这样:
addMemo(msg: string, done = false) { const memos: Memo[] = []; for (let i = 0; i < 10; i++) { const memo = new Memo(`${msg}-${i}`); memo.letsDance(); memos.push(memo); } // 直接用push添加多个元素,不替换原数组引用 this.messages.push(...memos); return memos.shift(); }
这样修改后只会触发一次组件渲染,因为我们是在原数组上做修改,而非替换整个数组。
2. 确保所有状态修改都在action中完成
如果Memo类里的letsDance方法修改了observable属性,一定要把它标记为action,这样MobX会把循环里的10次修改批量处理,不会每次都触发渲染:
class Memo { isDancing = false; constructor(public text: string) {} @action letsDance() { this.isDancing = true; // 其他状态修改操作 } } // 或者用decorate方式标记 decorate(Memo, { isDancing: observable, letsDance: action });
加上action标记后,循环里的10次letsDance调用会被批量处理,只会触发一次渲染。
3. 优化组件的observable追踪范围
如果App组件不需要追踪整个messages数组的所有元素,你可以拆分组件缩小追踪范围:把单个Memo的渲染逻辑抽成独立子组件,并用observer包裹。这样只有当单个Memo的属性变化时,才会渲染对应的子组件,父组件App不会因为数组元素增加而重新渲染(除非父组件依赖数组长度等属性)。
4. 正确使用实例缓存
你当前用useObservable(new MyModel())的方式不太合适,因为MyModel已经通过decorate标记为observable类了。正确做法是用useMemo缓存实例,避免每次组件渲染都创建新实例:
const App = () => { const myModel = useMemo(() => new MyModel(), []); return useObserver(() => ( <div> {/* 你的组件内容 */} </div> )); };
总结
最直接有效的方案是改用MobX数组的push方法替代concat+赋值,同时确保所有修改状态的方法都被标记为action,这样就能避免不必要的重复渲染。
内容的提问来源于stack exchange,提问作者Chance

