React 表单进化论:受控组件 vs 非受控组件

74 阅读3分钟

前言

在 React 开发中,处理表单(Form)是我们最常见的交互场景之一。无论是登录注册,还是复杂的配置页面,都离不开 input 框的使用。
在 React 的设计哲学中,表单组件主要分为两大流派:受控组件和 非受控组件。它们各有千秋,理解它们的区别和适用场景,能帮我们在未来的开发中做出更优雅的技术选型。

一、受控组件

所谓“受控”,是指 input 框的 value 属性不再由 DOM 自身维护,而是完全被 React 组件的 State(状态) 所控制。这也是 React 官方推荐的“唯一数据源”模式。

1. 实现步骤

要实现一个受控组件,通常需要以下四步闭环:

  • 声明状态:在组件中使用 useState 声明一个变量来存储表单的值。
  • 绑定值:将状态数据赋值给 input 的 value 属性。
  • 监听变化:绑定 onChange 事件,监听用户的输入行为。
  • 更新状态:在事件处理程序中,拿到最新的值并调用 setState 更新状态。

2. 代码示例

import { useState } from 'react';

export default function ControlledInput() {
    // 1. 声明状态,并给予初始值
        const [message, setMessage] = useState('大家好');
        function changeHandler(e) {
        // 4. 将文本框的最新值同步给 state
        // 注意:这里 e.target.value 获取的是用户刚刚输入后的最新值
            setMessage(e.target.value);
        // 提示:setState 是异步的,紧接着打印 message 可能还是旧值,这是正常现象
    }

 return (
        <div>
            {/* 2. & 3. 绑定 value 和 onChange */}
            <input type="text" value={message}  onChange={changeHandler}/> 
            <p>当前状态值: {message}</p>
        </div>
    )
}

3. 核心原理解析

为什么必须绑定 onChange?

如果你只设置了 value={message} 而不绑定 onChange,你会发现输入框变成了“只读”状态。这是因为 React 的渲染机制决定了视图是状态的函数。如果 State 没有改变,React 就会强制 input 渲染旧的 value 值。 只有通过 onChange 触发状态更新,React 重新渲染组件,input 的 value 才会变成新的值。这就是 “数据驱动视图”

二、非受控组件

非受控组件更像传统的 HTML 表单。数据主要存储在 DOM 节点中,而不是 React 的 State 中。如果你需要获取值,必须“主动”去 DOM 里拿。

1. 实现方式

非受控组件通常使用 useRef 钩子来直接操作 DOM。

2. 代码示例

import { useRef } from 'react';

    export default function UncontrolledInput() {
    // 1. 创建一个 ref 对象
    const inputRef = useRef(null);
    function login() {
        // 2. 在需要的时候(比如点击按钮),通过 ref.current 访问 DOM 节点获取值
        console.log("用户输入的内容是:", inputRef.current.value);
    }

return (
        <div>
            {/* 绑定 ref */}
            <input type="text" ref={inputRef}/>
            <button onClick={login}>登录</button>
        </div>
    )
}

3. 适用场景

非受控组件虽然不是 React 的“主流”,但在某些场景下非常有用:

  • 文件上传 <input type="file" />:文件输入通常是只读的,必须由 DOM 处理。
  • 集成第三方库:当使用非 React 编写的底层 DOM 库时。
  • 简单表单:不需要即时验证,只需要在提交瞬间获取值的场景。

三、巅峰对决:如何选择?

为了帮你更好地做决定,我们将两者的特点整理如下:

特性受控组件 (Controlled)非受控组件 (Uncontrolled)
数据来源React State (组件状态)DOM (节点本身)
数据获取实时获取 (onChange)只有在需要时获取 (通过 ref)
实时验证容易 (如:输入时检查格式)较麻烦
代码量相对较多 (需要写处理函数)相对较少
推荐指数⭐⭐⭐⭐⭐ (官方推荐)⭐⭐⭐ (特定场景使用)

结语

  • 如果你需要即时反馈(例如:输入密码时显示强度、限制输入字符长度、根据输入内容动态禁用按钮),请务必使用 受控组件。
  • 如果你只是想做一个简单的表单,或者处理 文件上传,不想写太多的 state 逻辑,非受控组件 是一个轻量级的选择。