选受控还是非受控取决于状态由父组件还是子组件管理:受控组件由父组件统一维护状态,适用于需联动、校验、重置等场景;非受控组件由dom自行管理,适合轻量、独立操作;混合模式需严格按value是否传入划分边界,避免状态混乱。

选受控还是非受控,关键看状态该由谁管:父组件统一维护就是受控,子组件自己记着就是非受控。这不是写法偏好,而是数据所有权的明确划分。
受控组件:适合需要外部干预的场景
当表单要联动、校验、重置,或值需与其他组件同步时,必须用受控模式。比如“启用开关”控制另一个字段显隐,或提交前统一校验所有输入,这些都依赖父组件掌握最新值。
- value 必须由 props 传入,不能在子组件里用 useState 初始化 fallback 值
- 每次输入都要触发 onChange 并通知父组件,父组件决定是否更新自己的 state
- 避免出现 “changing an uncontrolled input to be controlled” 警告——初始 value 为空字符串、null 或 undefined 都行,但不能不传
非受控组件:适合轻量、独立、一次性操作
搜索框临时过滤、模态框里的草稿输入、纯展示型编辑器,这类不需要父组件实时干预的 UI,用非受控更简洁。它把值交给 DOM 自己管,组件只在需要时读取。
- 用 defaultValue 初始化,后续变更完全由浏览器处理,父组件改 defaultValue 不会重置输入框
- 读值时用 useRef + current.value,不要监听 value 变化(它根本不会变)
- 适合和原生 JS 逻辑或第三方库集成,也更适合高频输入(如实时搜索),避免频繁 re-render
混合模式:能不用就别用,要用就得划清边界
极少数情况(比如封装通用表单控件)需要同时支持两种模式。判断依据只能是 value 是否被传入且不为 undefined:
- 有 value → 按受控走:不碰内部 state,只响应 onChange
- 没 value → 按非受控走:用 useRef 管值,忽略 value props 的任何变化
- 严禁在受控分支里写 useState 记录 value,也禁止在非受控分支里监听 value 更新
不复杂但容易忽略:状态归属权一旦定下,整个数据流就定了。选错模式,后期不是加 patch 就是推倒重来。










