React :受控组件与非受控组件详解

94 阅读5分钟

一、表单处理的痛点与 React 的解决方案

在 Web 开发中,表单是用户与程序交互的核心入口。React 对表单的处理方式与其他框架(如 Vue 或原生 JavaScript)有显著差异。它通过 受控组件非受控组件 两种模式,帮助开发者更灵活地管理表单数据。

1.1 传统表单处理的局限性

在传统 HTML 表单中,输入框的值由 DOM 本身管理,然而,这种方式却存在以下问题:

  • 状态分散在 DOM 和 JavaScript 之间
  • 难以实现数据验证和格式化
  • 无法实现响应式更新
<input type="text" id="username" />
<script>
  const input = document.getElementById('username');
  input.addEventListener('input', (e) => {
    console.log('用户输入:', e.target.value);
  });
</script>

React 的核心是 单向数据流声明式 UI。它要求组件的状态完全由 React 控制,因此引入了两种表单处理方案:

  • 受控组件:将表单状态交由 React 显式管理
  • 非受控组件:利用 DOM 原生行为管理状态

二、受控组件详解:React 的推荐方式

2.1 核心原理

代码示例:基础受控输入框

function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

功能说明

这段代码实现了一个最基础的受控输入框,其将输入框的值与 React 状态绑定,当用户输入时,通过 onChange 事件实时更新状态,状态更新后,React 自动重新渲染输入框显示最新值。

分析:

  1. useState Hook

    • useState('') 创建了一个响应式状态变量 value
    • 状态变更函数 setValue 用于更新值
  2. value 属性绑定

    • value={value} 将输入框的值与状态同步
    • 这是 React 控制表单的核心机制
  3. onChange 事件

    • 监听输入变化并调用 setValue
    • 形成「输入 → 状态更新 → 重新渲染」的闭环
  4. 单向数据流

    • 数据流动方向:状态 → 输入框
    • 与传统双向绑定(如 Vue 的 v-model)形成对比

2.3 实战场景:实时验证

function ControlledInput() {
  const [value, setValue] = useState('');
  const [error, setError] = useState(null);

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    
    if (newValue.length < 6) {
      setError('密码至少6位');
    } else {
      setError(null);
    }
  };

  return (
    <div>
      <input 
        value={value} 
        onChange={handleChange} 
        placeholder="请输入密码" 
      />
      {error && <p style={{color: 'red'}}>{error}</p>}
    </div>
  );
}

功能说明

这段代码实现了实时密码强度检测功能,当用户输入不足6位时显示红色错误提示,并在输入合法后自动清除错误提示。

分析

  1. 条件渲染

    • {error && <p>... 实现根据状态显示/隐藏错误提示
    • 这是 React 条件渲染的典型用法
  2. 状态联动

    • 通过 setValue 更新输入值
    • 通过 setError 更新错误提示
    • 两个状态相互关联但独立管理
  3. 正则表达式验证

    • 可扩展为更复杂的验证逻辑(如正则表达式)
    • 示例中使用了最基础的长度判断

2.4 优势与适用场景

优势说明
实时反馈输入变化立即触发状态更新
易于验证可在 onChange 中直接验证
响应式更新状态变化自动触发 UI 重绘

三、非受控组件详解:DOM 原生行为的延伸

3.1 核心原理

代码示例:

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

  const handleSubmit = (e) => {
    e.preventDefault();
    alert('输入值:' + inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} type="text" />
      <button type="submit">提交</button>
    </form>
  );
}

功能说明

这段代码实现了通过 ref 直接访问 DOM 元素,并在表单提交时弹出输入值而且不依赖 React 状态管理。

分析:

  1. useRef Hook

    • useRef(null) 创建可变引用对象
    • inputRef.current 访问真实 DOM 元素
  2. 事件处理

    • onSubmit 捕获表单提交事件
    • e.preventDefault() 阻止默认提交行为
  3. DOM 操作

    • inputRef.current.value 获取原始 DOM 值
    • 与 React 状态无关的直接操作

3.3 实战场景

function LoginForm() {
  const usernameRef = useRef(null);
  const passwordRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const username = usernameRef.current.value;
    const password = passwordRef.current.value;
    console.log('用户名:', username);
    console.log('密码:', password);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={usernameRef} placeholder="用户名" />
      <input ref={passwordRef} type="password" placeholder="密码" />
      <button type="submit">登录</button>
    </form>
  );
}

功能说明

这段代码组件实现了登录表单的基本结构,实现了用户名和密码字段的输入收集,并在提交时打印表单数据。

分析

  1. 多 ref 使用

    • 为每个输入框创建独立的 ref
    • 通过 current.value 获取各自值
  2. 表单事件处理

    • onSubmit 处理表单提交逻辑
    • 同时防止页面刷新
  3. 安全性考虑

    • 密码字段使用 type="password"
    • 实际开发中应加密传输数据

3.4 优势与适用场景

优势说明
性能更优避免频繁状态更新
简化逻辑不需要维护额外状态
与第三方库兼容适合封装原生 DOM 行为

四、受控 vs 非受控组件

特性受控组件非受控组件
状态管理React 状态(useState)DOM 原生状态
数据流单向数据流双向数据流
性能频繁更新可能影响性能性能更优
使用复杂度较高(需处理状态和事件)较低(直接操作 DOM)
适用场景实时验证/格式化简单表单提交
示例代码value={state} + onChangeref + current.value

4.1 综合案例对比

受控组件版本

function ControlledForm() {
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');

  return (
    <form>
      <input 
        value={username} 
        onChange={(e) => setUsername(e.target.value)} 
        placeholder="用户名" 
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
        placeholder="邮箱" 
      />
    </form>
  );
}

非受控组件版本

function UncontrolledForm() {
  const usernameRef = useRef(null);
  const emailRef = useRef(null);

  const handleSubmit = () => {
    console.log('用户名:', usernameRef.current.value);
    console.log('邮箱:', emailRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={usernameRef} placeholder="用户名" />
      <input ref={emailRef} placeholder="邮箱" />
      <button type="submit">提交</button>
    </form>
  );
}

对比分析

  1. 状态管理方式

    • 受控组件:通过 useState 显式管理
    • 非受控组件:依赖 DOM 原生状态
  2. 数据流方向

    • 受控组件:单向数据流(状态 → 输入框)
    • 非受控组件:直接访问 DOM 值
  3. 开发体验

    • 受控组件:需要处理更多事件和状态
    • 非受控组件:代码更简洁直观

五、总结

选择策略:

  • 优先使用受控组件:这是 React 推荐的方式,尤其适合需要实时反馈的场景
  • 使用非受控组件:当性能敏感或需要直接操作 DOM 时
  • 混合使用:复杂表单中可对不同字段采用不同策略