选择状态的最小集合
选择状态的最小集合 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
状态写得越多,同步成本越高。这一课教一个设计动作:对着已有的状态清单逐个追问「它能不能算出来」,把能算的删掉,换成渲染时的派生计算。读完你能把一个重复字段缠身的功能收敛到最小状态集。
问题场景
任务清单第一版存了三个 state:
const [tasks, setTasks] = useState([]);
const [doneCount, setDoneCount] = useState(0); // 完成数量
const [visibleTasks, setVisibleTasks] = useState([]); // 筛选后的列表
勾选一个任务要连调三个 setState:改 tasks、算 doneCount、更新 visibleTasks。测试很快抓到 bug:快速连点两次勾选,完成数偶尔少 1——状态多了,忘同步只是时间问题。
核心解释
React 官方的判断标准只有一句话:如果一个值能由已有 props 或已有 state 算出来,它就不该是 state。 上面代码里,doneCount 是 tasks.filter(t => t.done).length,visibleTasks 是 tasks 过滤排序的结果——两者都是 tasks 的影子,删掉,渲染时现算:
const doneCount = tasks.filter((t) => t.done).length;
const visibleTasks =
filter === "all" ? tasks : tasks.filter((t) => t.done);
每次渲染重新算一遍,永远和 tasks 一致,不存在「忘了同步」。任务量几百条时这点计算完全无感——先按正确性写,真出现性能问题再上 useMemo 缓存。
判断一个值该不该是 state,可以按顺序问三个问题:
- 它能否由 props 算出?能,就是派生值;
- 它能否由已有 state 算出?能,也是派生值;
- 它会随用户操作变化且算不出来吗?只有这才是 state。
还有一种典型冗余:把「初始值 + 变更历史」都存起来。比如编辑表单存 { draft, original } 两个字段,其实 original 挂载时存一次即可,draft 变化时 hasChanges 直接算 draft !== original。存最小事实,其余靠计算还原。
示例拆解
实验区是同一个任务清单的两种实现,关键点如下:
- Bug 版:三个 state 各自为政,勾选时
setDoneCount被故意写漏一个分支——勾选第 3 个任务后计数停在 2,界面自相矛盾;「手动同步」按钮展示了每次操作要写多少同步代码; - 修复版:只存
tasks和filter两个 state,doneCount与visibleTasks在渲染函数体里直接算,勾选处理只有一个setTasks; - 两个版本的界面结构完全相同——说明删状态不影响功能,只影响正确性风险和维护成本;
- 数据面板实时显示三者的值,修复版的派生值永远跟着
tasks走。
验证方式:两边各快速勾选、取消勾选十次,Bug 版迟早露出不一致,修复版永远自洽。
动手任务
打开本课实验区,完成三个小改动:
- 给修复版加「按名称排序」开关,观察你只需改派生计算那一行,状态零新增。
- 在 Bug 版补上漏掉的同步分支,数一数加这一个功能要改几处。
- 把修复版的
filter也删掉试试——哪里坏了?想清楚filter为什么必须留(用户意图,算不出来)。
常见错误
- 把派生值存成 state:多一个就多一处同步,迟早不一致;改成渲染时计算。
- 在 useEffect 里手动同步派生值:
useEffect(() => setDoneCount(...), [tasks])是把冗余状态又绕回来了,且多一次渲染;直接算。 - 连 props 都复制进 state:
const [copy] = useState(props.value)之后 props 更新了 copy 不动,界面用旧值;props 就该直接用。 - 反向优化过头:把「用户输入的搜索词」也试图从别处推导——它是用户意图的源头,算不出来,老老实实做 state。
检查与复盘
完成下方检查题确认理解。自查清单:
- 写完功能后能对着 state 清单逐个追问「能不能算出来」
- 派生计算直接写在渲染函数体里,不用 useEffect 同步
- 能说出哪些值必须做 state:用户意图、外部输入、算不出来的事实
参考资料
- React 官方文档:选择状态结构 —— 最小状态集的官方原则
- React 官方文档:你可能不需要 Effect —— 「派生状态不要用 Effect 同步」的官方说明
读完这一课了?
标记完成后,工作台会安排接下来的学习。