派生状态是任何可以从现有状态计算出来的值,而不是单独存储的值。原则是:如果可以计算,就不要存储它——在每次渲染时派生它。存储可派生的值容易导致它们不同步。
反模式:存储可以计算的内容
jsx
() {
[items, setItems] = ([]);
[total, setTotal] = ();
() {
([...items, item]);
(total + item.);
}
}
现在 total 是 items 中已有信息的第二个副本。items 的每次变化也必须更新 total,而一旦漏掉一个更新(一个删除、一个数量变化),它们就不一致了,UI 显示的总数就错了。
// ✅ compute total from items — always correct, never out of sync
function Cart() {
const [items, setItems] = useState([]);
const total = items.reduce((sum, i) => sum + i.price, 0); // derived each render
function addItem(item) {
setItems([...items, item]); // ONE update; total recomputes automatically
}
}
通过派生 total,没有额外需要同步的内容——修改 items 时,总数总是正确的。
const sortedFiltered = useMemo(
() => items.filter(i => i.active).sort(compareFn), // expensive derivation
[items] // recompute only when items change
);
对于计算量大的派生(大型筛选/排序),useMemo/computed/选择器会缓存结果,使其仅在依赖项改变时重新计算——但它仍然是派生的,而不是作为独立状态存储。
Totals/counts, filtered or sorted lists, fullName from first+last,
isValid from form fields, formatted/displayed strings, "is everything selected?"
不复制派生状态是单一真实来源原则的直接应用——它消除了整个bug类别,即多个副本失去同步的问题。
"派生而不是存储"这条规则通过保持状态最小化并确保 UI 保持一致来简化设计。
识别什么是可派生的(仅在性能需要时才使用 useMemo/computed/选择器,而不是创建第二个真实来源)是干净的、抗bug能力强的状态设计的标志。