React 的更新检测本质是引用比较:新旧 state 不是同一对象才重渲染。不可变更新要求改数据时绝不修改原对象,而是返回一份拷贝——数组用展开、filter、concat、map,对象用展开与属性覆盖,深层结构借助 Immer 这样的库把过程写得更直观。
直接 push、splice 或者修改对象字段是最隐蔽的 bug 来源:引用没变,React 认为数据没变化,界面停在旧状态;更糟的是若同周期内又比较出新引用,浅比较体系可能渲染出新旧混杂的视图。传 props 也一样,内联对象/数组每次渲染都是新引用,会击穿 memo 的浅比较。
import { useState } from 'react'; function Todos() { const [todos, setTodos] = useState([ { id: 1, text: '学 React', done: false }, ]); const add = () => setTodos([...todos, { id: 2, text: '新任务', done: false }]); const toggle = (id) => setTodos( todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)) ); return ( <ul> {todos.map((t) => ( <li key={t.id} onClick={() => toggle(t.id)}> {t.done ? '已完成' : '待办'}:{t.text} </li> ))} <button onClick={add}>添加</button> </ul> ); }