如何从Mobx observable数组删除元素且不触发所有消费组件重渲染
问题原因
这不是Mobx配置错误,重渲染是两个写法问题共同导致的:
- 外层
Todos组件是observer,订阅了todosStore.todos的变化,你无论用splice修改原数组还是filter替换数组,都会触发数组的变更通知,导致外层组件重渲染。 - 外层组件重渲染时,
map循环内每次都会生成新的onDelete、onNameChange匿名函数,传递给Todo组件的props引用发生变化。observer组件默认会对props做浅比较,函数引用变化就会触发子组件重渲染,因此所有剩余Todo都会跟着重渲染。
解决方案
核心是让Todo组件的props保持稳定,最简单的改法是把回调逻辑移到Todo组件内部,不要通过父组件传递匿名回调:
1. 修改Todo组件
import { observer } from "mobx-react-lite"; import { combinedStores } from "."; export default observer(({ todo }) => { const { todosStore } = combinedStores; const handleDelete = () => todosStore.removeTodo(todo.id); const handleNameChange = (e) => todosStore.editTodoName(todo.id, e.target.value); return ( <div style={{padding:'10px',margin:'10px'}}> <p>ID: {todo.id}</p> <input onChange={handleNameChange} value={todo.name} /> <p>Completed: {todo.completed ? 'true' : 'false'} <button onClick={handleDelete} className="btn btn-danger">Delete</button> </p> </div> ) })
2. 简化外层Todos组件
import { observer } from "mobx-react-lite"; import { combinedStores } from "."; import Todo from "./Todo"; export default observer(() => { const { todosStore } = combinedStores; return ( <div> {todosStore.todos.map(todo => ( <Todo key={todo.id} todo={todo} /> ))} </div> ) })
修改后Todo组件的props只有todo,只要todo对象本身没有被替换,引用就不会变,即使外层组件重渲染,子组件浅比较props不会触发重渲染,只有被删除的Todo会被移除,其余Todo都不会重复渲染。
额外优化(可选)
如果连外层组件的重渲染都想避免,可以用<Observer>组件单独包裹列表渲染部分,外层组件不需要加observer:
import { Observer } from "mobx-react-lite"; import { combinedStores } from "."; import Todo from "./Todo"; export default () => { const { todosStore } = combinedStores; return ( <div> <Observer> {() => todosStore.todos.map(todo => ( <Todo key={todo.id} todo={todo} /> ))} </Observer> </div> ) }
这种情况下只有<Observer>包裹的列表部分会在数组变化时重渲染,外层组件不会触发重渲染。
内容的提问来源于stack exchange,提问作者i.brod
相关产品推荐
相关产品推荐

