受控(Controlled)

React 是 single source of truth——值住在 state 里,DOM 只是显示:

const [text, setText] = useState('');
<input value={text} onChange={e => setText(e.target.value)} />

每次按键 → setText → re-render → value={text} 把新值写回 DOM。

优点

  • 随时能读、能改、能校验
  • 与其他 state 联动方便
  • 可以做受控行为(输入字母自动转大写、限制长度等)

缺点

  • 多打一个字 → 一次 re-render
  • 表单字段多(10+)时性能会显示出来

非受控(Uncontrolled)

DOM 是 source of truth——React 不管值,要用时用 ref 取:

const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} defaultValue="" />

const onSubmit = () => {
  const val = inputRef.current?.value;
};

注意是 defaultValue 而不是 value——value 一旦传就变受控了。

优点

  • 输入时不触发 React re-render,性能好
  • 代码量少
  • 与原生 form 行为兼容(reset / submit)

缺点

  • 实时校验麻烦(要监听 onChange 自己拿值)
  • 复杂联动写起来不优雅

怎么选

场景 推荐
一两个字段的搜索框 受控(简单)
大表单(10+ 字段) 非受控 + React Hook Form
实时校验 / 联动 受控
富文本 / 复杂编辑器 非受控(编辑器自己管状态)
文件上传 <input type="file" /> 必须非受控(浏览器安全限制)

React Hook Form:非受控的现代写法

import { useForm } from 'react-hook-form';

function Login() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  return (
    <form onSubmit={handleSubmit(d => console.log(d))}>
      <input {...register('email', { required: true })} />
      {errors.email && <span>必填</span>}
      <button>提交</button>
    </form>
  );
}

每个字段 ref 化,不触发 re-render。表单字段越多越香。详见 第 18 篇

半受控(混合)

const [text, setText] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
<input value={text} onChange={e => setText(e.target.value)} ref={inputRef} />

可以同时拿 React 状态 + 直接操作 DOM(如 focus)。ref 不会让组件从受控变非受控

切换之坑

<input value={text} />        // text 一开始是 undefined → React 报警 "uncontrolled to controlled"

修法:初值给 '' 而不是 undefined

const [text, setText] = useState('');   // ✅ 初值是 ''

checkbox / radio / select

  • <input type="checkbox" checked={x} onChange={...} />——属性叫 checked
  • <select value={x} onChange={...}>...——<option selected> 不用,统一通过 select.value
  • <input type="radio" name="..." value="a" checked={v==='a'} onChange={...} />

文件上传(必须非受控)

<input type="file" onChange={e => {
  const file = e.target.files?.[0];
  if (file) upload(file);
}} />

不能给 value 设置 file(浏览器禁止——避免你伪造用户选择的文件)。

→ 下一篇 状态管理库选型