先记住这个答案
React 会按照组件在树中的位置、类型以及 key 来判断是否延续同一个实例的状态。如果切换账号或文档意味着一整套编辑状态都应重新开始,可以给编辑子树设置对应业务 ID 作为 key。key 改变后旧子树卸载、新子树建立,其内部 state 与 ref 重新初始化;这比挂载后再用 Effect 逐项清空更准确地表达身份变化,但也会丢失子树内所有本地状态。
- 稳定业务 ID 表达不同组件身份
- key 改变会重建对应子树,不只清空一个字段
- 需保留的草稿应移到重建边界之外
为什么只改变 prop 不等于新的组件
假设同一个 Editor 从文档甲切到文档乙,父组件只改变 documentId。若其树位置和类型没有变化,React 通常会延续内部 state;用 documentId 初始化的输入值不会自动再初始化。这正是切换对象后草稿还停在旧内容的常见原因。
把 key 放在 Editor 上,会让这个位置上的组件身份随文档 ID 改变。它与手动调用多个 setter 的差异在于,重建也覆盖嵌套组件的本地状态和 ref,旧实例的清理逻辑会运行。父组件与其它兄弟节点是否保留,要看它们是否位于这个重建边界内。
编辑器切换时先决定哪些内容应保留
如果业务要求切换文档就放弃临时筛选、光标状态和未提交输入,文档 ID 可以成为整体编辑子树的 key。切换之前若有未保存内容,应先在事件流程中完成确认或保存;不能等新实例建立后,才试图从已经销毁的旧实例恢复输入。
若用户需要在多份文档之间保留草稿,就应把草稿按文档 ID 存在父层或专门的编辑状态中,再把当前文档的草稿交给子组件。是否重建展示控件与是否保留业务草稿是两个决策,可以同时选择重建编辑器并保留外层持久状态。
局部重置不应扩大成整棵树销毁
只想在地区变化时清空城市字段,通常在同一个事件或 reducer 转移中处理更直接。若给整个表单换 key,用户姓名、上传进度和焦点也会一起丢失。设计重置边界时,可以先列出应该保留与应该清除的状态,再决定 setter、reducer 或组件身份哪一种表达最合适。
key 应在同一个业务对象持续展示时保持稳定。随机数、时间戳或每次渲染重新产生的 ID 会让组件不断重建,导致输入焦点丢失、订阅反复建立和动画重新开始。将 key 从列表索引改成业务 ID 也要结合列表重排场景理解,而不是把所有 key 问题都当作重置需求。
容易答错的地方
- 把 key 当成传给子组件的普通参数
- React 使用 key 识别同级组件身份,子组件不能把它当作普通 props.key 读取。示例若需要请求文档内容,应另外传 documentId,避免在组件内部依赖读取 key 的错误写法。
- 用 key 修补所有状态设计问题
- 强制重建能让现象暂时消失,但可能同时破坏需要保留的状态。先明确为什么这是一个新的业务实例;如果只是一个字段变化,通常应优化状态归属与转移逻辑。
面试官还会怎么问?
更换子组件 key 会让父组件的状态一起丢失吗?
不会因为子组件换 key 就自动清空其父层状态。重建从 key 对应的节点开始,影响这棵子树;若父组件本身也被外层换了身份,才会连同它的状态一起重建。
key 改变后旧的请求会自动取消吗?
React 会执行相应 Effect 的清理,但请求能否取消取决于清理逻辑是否使用了 AbortController 等机制。还要处理不可取消操作的迟到结果,不能把卸载等同于网络服务已停止执行。
为什么在 Effect 里清空有时会出现旧值?
新的 props 进入渲染时,旧 state 仍然存在,Effect 运行后才安排新的状态更新。这种先用旧状态渲染再修正的流程会增加中间状态;身份确实变化时,key 能让新实例直接从初始状态开始。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。