为什么不能直接修改 state 对象或数组?
先理解原理和示例,再练习完整口述与追问。
回答要点
把已有状态当作只读快照,通过新对象表达变更;共享对象被原地修改会破坏旧快照,也可能因引用不变导致预期更新被跳过。嵌套更新要复制受影响路径,或采用项目已使用的不可变更新工具。
原理拆解
state 的对象引用是当前渲染快照的一部分。原地修改它既可能不能产生需要的更新信号,也会改变旧闭包看到的对象内容,破坏历史快照的理解。更新时只复制实际变化的路径,保留未变部分的引用,让 React 和记忆化逻辑能据引用判断哪些内容可能变了。
示例与推演
setUser(old => ({
...old,
address: { ...old.address, city: 'Shanghai' }
}))
setRows(old => old.map(row => row.id === targetId ? { ...row, done: true } : row))
第一段复制到 address 层,第二段只替换目标行,其余行复用原引用。先复制外壳再修改原嵌套对象,仍然属于原地改变旧状态。
易错点
不要为每次小更新都深拷贝整个状态树;那会让所有引用变化,增加性能和调试成本。更新路径很复杂时可以评估专门工具,但先确认团队约定。
参考回答可直接练习口述
React 把 state 当作过去渲染的只读快照。直接改对象不一定触发新渲染,还可能同时改掉旧闭包或其他组件共享的对象。我会生成新对象或数组,并复制所有实际修改的嵌套路径,再调用 setter。不是所有地方都深拷贝,而是保留未变化部分的引用,这也有利于比较和缓存。
面试官可能继续问追问与完整解答展开收起
追问
只复制最外层够吗?sort、splice 为什么容易误改源状态?每次都深拷贝整个状态是否必要?
追问回答
外层展开但直接改嵌套对象仍会影响旧状态,应沿修改路径复制。sort、splice 会改原数组,可先复制再操作或用不改变源数据的方法。全量深拷贝会增加成本并破坏未变化子对象的稳定引用,通常没有必要。