受控组件 vs 非受控组件

聊聊表单数据到底归谁管

先说人话:这两种模式的区别就是——表单的值到底归 React 管,还是归 DOM 自己管。

就这一句话,核心区别就在这。

受控组件:React 当家作主

什么是受控组件?

就是表单的值由 React 的 state 完全控制。你输入一个字,state 变一次,组件重新渲染一次。

怎么写?

function ControlledInput() {
  const [value, setValue] = useState("");

  const handleChange = (event) => {
    setValue(event.target.value); // 每次输入都更新 state
  };

  return (
    <input
      type="text"
      value={value} // 值由 state 决定
      onChange={handleChange} // 每次变化都更新 state
    />
  );
}

工作流程:

  1. 用户打字 -> 触发 onChange
  2. onChange 调用 setState 更新 state
  3. state 变了 -> 组件重新渲染
  4. 重新渲染后,value={value} 把新值塞回输入框

有啥优点?

  • 数据随时可控:state 就是"唯一真相",随时可以拿到最新值
  • 验证/格式化方便:每次输入都能拦截,想咋处理输入都行
  • 编程式控制:想重置输入?setValue("") 就行;想设值?setValue("hello") 就搞定
  • 符合 React 套路:数据自上而下流动,清楚明白

有啥缺点?

每次输入都触发 setState、重新渲染,性能上可能有点浪费——不过对于大多数场景,这点开销可以忽略不计。

非受控组件:DOM 自己管自己

什么是非受控组件?

表单的值由 DOM 自己管理,React 不管。React 想要值的时候,通过 ref 去 DOM 节点"拉取"。

怎么写?

function UncontrolledInput() {
  const inputRef = useRef(null);

  const handleSubmit = () => {
    // 需要用值的时候,去 DOM 里拿
    alert("输入的值是: " + inputRef.current.value);
  };

  return (
    <>
      {/* 不需要 value 属性,用 defaultValue 设置初始值 */}
      <input type="text" ref={inputRef} defaultValue="初始值" />
      <button onClick={handleSubmit}>提交</button>
    </>
  );
}

有啥优点?

  • 更像原生 HTML:写法跟普通表单一样
  • 代码更简单:不用写 onChange,不用写 setState
  • 性能更好:每次输入不触发 React 渲染
  • 集成第三方库方便:有些库要自己操作 DOM,用 ref 直接拿到节点,很顺滑

有啥缺点?

  • 数据不可控:你想在用户输入过程中做验证?难
  • 想重置值?麻烦:得手动操作 DOM,违背了 React 的套路
  • 不知道当前值是啥:除非你去 ref 里拿

到底该用哪个?

大多数情况下,用受控组件。

React 官方推荐受控组件,因为数据可控、逻辑清晰、符合 React 思想。

用受控组件的情况:

  • 需要即时验证输入(密码强度、格式校验)
  • 需要根据输入动态控制其他 UI(输入为空时禁用提交按钮)
  • 需要强制格式化(比如自动转大写、格式化手机号)
  • 表单逻辑复杂,多个字段相互依赖
  • 需要编程式重置或赋值

用非受控组件的情况:

  • 表单极其简单,只需要在提交时拿一次值
  • 要接第三方库(图片上传、富文本编辑器),它们自己要操作 DOM
  • 输入极其频繁(比如实时绘图中选颜色),性能实在敏感

总结

受控组件非受控组件
数据归谁管React stateDOM 自己
写法value={state} + onChangeref + defaultValue
性能每次输入都渲染更轻量
控制力随时可控
适用场景大多数场景简单表单、集成第三方库

记住一句话:React 能管的就用受控组件,React 管不了的或者不想管的,就用非受控组件。