先记住这个答案
非受控输入挂载时可以使用 defaultValue 建立默认内容,随后用户编辑的是控件当前值。改变 defaultValue 不应被用作持续覆盖当前输入的方式,尤其不能期待它可靠替换用户已经编辑的内容。若当前值必须跟随 React 数据,使用受控 value;若切换到新业务对象需要整体重新开始,可以通过稳定业务 key 重建表单,或按明确的原生表单重置语义处理。
- 默认值和当前编辑值是两层概念
- 异步回填先决定是否允许覆盖用户修改
- 重建会丢失子树状态,key 应表达业务身份
异步加载为什么容易暴露这个差异
编辑页首次渲染时数据尚未到达,非受控 input 的 defaultValue 使用空字符串。请求完成后父组件重新渲染,即便新的默认属性与用户档案有关,当前控件也不是受控 value 绑定。尤其用户已经输入内容时,不能期望每次外部默认值变化都强行改写正在编辑的值。
可以等必要数据准备好再建立编辑表单,或者从一开始使用受控值,并在数据到达时依据编辑状态决定是否回填。关键问题是业务时序:请求回来时用户可能已经开始编辑。无条件覆盖会丢失输入,完全忽略又可能遗漏重要初始数据,应有清楚的加载和草稿规则。
切换账号与重置当前字段不是同一种需求
如果从账号 A 切到账号 B 代表整套编辑器都是新的实例,可以给编辑子树设置账号 ID 作为 key。新实例采用新的默认数据,旧实例的本地 state、ref 与嵌套控件状态也会重建。需要保留的跨账号草稿应存放在这个重建边界之外。
若只是想恢复当前表单的默认输入,可以评估 form.reset,并明确默认值来源;如果字段是受控的,还必须更新 React state。只想清空一个字段就给整个页面换 key,会额外丢失焦点、展开状态和其它未保存输入,重置范围应该与实际业务要求一致。
命令式赋值需要知道自己绕过了什么
对于由浏览器维护的非受控控件,某些明确的命令式操作可以通过 ref 修改当前 DOM value,但这不等于自动更新其它业务状态,也不会替你派发所有需要的事件。表单库、校验提示和脏状态若有自己的记录,就需要使用它们约定的更新接口。
不要给受控 input 直接改 DOM 后期待它长期保持,后续 React 更新可能再次应用权威 state。排查问题时应同时看 value、defaultValue、组件 key 和是否重新挂载,辨认到底是当前值没有被控制,还是输入实例不断被重建导致用户编辑丢失。
容易答错的地方
- 每次渲染换一个随机 key 就能可靠回填
- 随机 key 会持续重建控件,造成焦点、选择和输入丢失。key 应在同一业务对象持续展示时稳定,只在身份确实变化时改变。
- defaultValue 与 value 可以同时写以便兼容
- 这会让控制模式和数据来源含糊。选择持续受控或提供初始默认值,并用明确的加载与重置流程处理变化,不要把两个入口都交给同一字段。
面试官还会怎么问?
只在第一次读取 props 初始化 state 会自动跟着变吗?
不会,state 初始化与后续 prop 变化是不同阶段。如果需要持续受控,应由明确所有者更新 state;如果是独立草稿,则需要定义新业务对象到来时的保留与重置规则。
form.reset 会清掉所有表单库的错误状态吗?
不一定,原生重置与表单库内部状态不是同一套机制。应查看该库的重置协议,明确默认数据、校验错误、脏标记和文件选择的处理范围。
服务端渲染用 defaultValue 有问题吗?
它可以用于提供非受控输入的初始值,但客户端初始数据应与服务端输出保持一致。后续异步编辑和回填仍需明确模式,服务端输出并不会把 defaultValue 变成持续绑定。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。