《React 受控组件 vs 非受控组件:一篇吃透表单处理精髓》

0 阅读8分钟

React 受控组件 vs 非受控组件:一篇吃透表单处理精髓

在 React 开发中,表单处理是高频场景——登录注册、评论提交、信息录入,几乎每个项目都会用到。但很多新手都会困惑:同样是获取表单输入值,为什么有的用 useState,有的用 useRef?这其实对应了 React 表单处理的两种核心方式:受控组件非受控组件

很多人分不清两者的区别,盲目使用导致表单出现“无法输入”“值获取不到”“性能冗余”等问题。本文将从「核心疑问出发」,拆解两者的定义、用法、区别,结合实战代码演示,帮你彻底搞懂什么时候用受控、什么时候用非受控,看完直接落地项目。

一、核心疑问:怎么拿到 React 表单的值?

原生 HTML 中,我们可以通过 DOM 直接获取表单元素的值,比如 document.querySelector('input').value。但 React 遵循“单向数据流”原则,不推荐直接操作 DOM,因此提供了两种更规范的方式获取表单值,对应两种组件类型。

先看一个最基础的示例,直观感受两者的差异:

import { useState, useRef } from 'react';

export default function App() {
  // 受控组件:用状态控制输入框
  const [value, setValue] = useState("")
  // 非受控组件:用 ref 获取 DOM 值
  const inputRef = useRef(null);

  // 表单提交逻辑
  const doLogin = (e) => {
    e.preventDefault(); // 阻止页面刷新
    console.log("非受控输入值:", inputRef.current.value); // 非受控获取值
    console.log("受控输入值:", value); // 受控获取值
  }

  return (
    <form onSubmit={
      {/* 受控输入框:value 绑定状态,onChange 更新状态 */}
      <input 
        type="text" 
        value={) => setValue(e.target.value)} 
        placeholder="受控输入框"
      />
      {/* 非受控输入框:ref 关联 DOM,无需绑定状态 */}
      <input 
        type="text" 
        ref={受控输入框"
        style={{ marginLeft: '10px' }}
      />
      <button type="submit" style={提交
  )
}

上面的代码中,两个输入框分别对应受控和非受控两种方式,核心差异在于「值的控制者」不同——一个由 React 状态控制,一个由 DOM 原生控制。

二、逐字拆解:什么是受控组件?

1. 核心定义

受控组件:表单元素的值由 React 状态(useState)完全控制,输入框的显示值 = 状态值,输入行为通过 onChange 事件更新状态,从而实现“状态 ↔ 输入框”的联动。

核心逻辑:状态驱动 DOM,符合 React 单向数据流原则——数据从状态流向 DOM,DOM 输入行为通过事件反馈给状态,形成闭环。

2. 核心用法(必记)

实现一个受控组件,必须满足两个条件:

  • 给表单元素绑定 value={状态值},让状态决定输入框显示内容;
  • 绑定 onChange 事件,通过 e.target.value 获取输入值,调用 setState 更新状态。

3. 实战:多字段受控表单(登录注册场景)

实际开发中,表单往往有多个字段(如用户名、密码),此时可以用一个对象状态管理所有字段,配合事件委托简化代码:

import { useState } from "react"

export default function LoginForm() {
  // 用对象状态管理多个表单字段
  const [form, setForm] = useState({
    username: "",
    password: ""
  });

  // 统一处理所有输入框的变化
  const handleChange = (e) => {
    // 解构事件目标的 name 和 value(输入框需设置 name 属性)
    const { name, value } = e.target;
    // 更新状态:保留原有字段,修改当前输入字段(不可直接修改原对象)
    setForm({
      ...form, // 展开原有表单数据
      [name]: value // 动态更新对应字段
    })
  }

  // 表单提交
  const handleSubmit = (e) => {
    e.preventDefault();
    // 直接从状态中获取所有表单值,无需操作 DOM
    console.log("表单数据:", form);
    // 实际开发中:这里可做表单校验、接口请求等逻辑
  }

  return (
    <form onSubmit={<div style={<input 
          type="text" 
          placeholder="请输入用户名" 
          name="username" /Change}
          value={form.username} // 绑定状态值
          style={{ padding: '6px' }}
        />
      <div style={>
        <input 
          type="password" 
          placeholder="请输入密码" 
          name="password" / 绑定状态值
          style={{ padding: '6px' }}
        />
      <button type="submit" style={注册
  )
}

4. 受控组件的关键细节

  • ⚠️ 只写 value={状态} 不写 onChange,输入框会变成「只读」——因为状态无法更新,输入框值永远固定;
  • 状态更新是异步的,但不影响表单输入(React 会批量处理状态更新,保证输入流畅);
  • 适合做「实时操作」:比如实时表单校验、输入内容实时展示、表单字段联动(如密码强度提示)。

三、逐字拆解:什么是非受控组件?

1. 核心定义

非受控组件:表单元素的值由 DOM 原生控制,React 不干预输入过程,而是通过 useRef 获取 DOM 元素,再读取其 current.value 获取输入值。

核心逻辑:DOM 驱动数据,和原生 HTML 表单逻辑一致,React 只做“被动获取”,不主动控制输入值。

2. 核心用法(必记)

实现一个非受控组件,只需一步:

  • useRef(null) 创建 Ref 对象,绑定到表单元素的 ref 属性;
  • 需要获取值时,通过 ref.current.value 读取(通常在提交、点击等事件中获取)。

可选:用 defaultValue 设置初始值(仅首次渲染生效,后续修改不影响)。

3. 实战:非受控评论框(一次性提交场景)

评论框、搜索框等“一次性提交”场景,无需实时监控输入,用非受控组件更简洁高效:

import { useRef } from 'react';

export default function CommentBox() {
  // 创建 Ref 对象,关联 textarea 元素
  const textareaRef = useRef(null);

  // 提交评论逻辑
  const handleSubmit = () => {
    // 防御性判断:避免 ref.current 为 null(极端场景)
    if (!textareaRef.current) return;
    // 获取输入值
    const comment = textareaRef.current.value.trim();
    // 表单校验
    if (!comment) return alert('请输入评论内容!');
    // 提交逻辑
    console.log("评论内容:", comment);
    // 提交后清空输入框(直接操作 DOM)
    textareaRef.current.value = "";
  }

  return (
    <div style={<textarea 
        ref={        placeholder="输入评论..."
        style={{ width: '300px', height: '100px', padding: '10px' }}
        defaultValue="请输入你的看法..." // 初始值(可选)
      />
      <button 
        onClick={={{ padding: '6px 16px', marginTop: '10px' }}
      >
        提交评论
      
  )
}

4. 非受控组件的关键细节

  • ⚠️ 不要用 value 绑定状态(否则会变成受控组件),初始值用 defaultValue
  • Ref 对象的 current 在组件首次渲染后才会指向 DOM,因此不能在组件渲染时直接读取 textareaRef.current.value(会报错);
  • 适合做「一次性操作」:比如文件上传( 必须用非受控)、简单搜索框、一次性提交的表单。

四、核心对比:受控组件 vs 非受控组件(必背)

很多人纠结“该用哪个”,其实核心看「是否需要实时控制输入」,用表格清晰对比两者差异,一目了然:

对比维度受控组件非受控组件
值的控制者React 状态(useState)DOM 原生控制
核心依赖useState + onChangeuseRef
值的获取方式直接读取状态(如 form.username)ref.current.value
初始值设置useState 初始值(如 useState(""))defaultValue 属性
是否触发重渲染输入时触发(onChange 更新状态)输入时不触发(无状态变化)
适用场景实时校验、表单联动、实时展示一次性提交、文件上传、性能敏感场景
优点可实时控制,符合 React 单向数据流,易维护简洁高效,无需频繁更新状态,性能更好
缺点频繁触发重渲染,代码量稍多无法实时控制,需手动操作 DOM,不易做联动

五、实战总结:什么时候该用哪个?(重点)

不用死记硬背,记住两个核心原则,就能快速判断:

1. 优先用受控组件的情况

  • 表单需要「实时校验」(如用户名长度限制、密码强度提示);
  • 表单字段需要「联动」(如勾选“记住密码”才显示“密码确认”);
  • 需要「实时展示输入内容」(如输入时同步显示剩余字符数);
  • 表单数据需要和其他组件共享、联动(如跨组件传递表单值)。

2. 优先用非受控组件的情况

  • 表单是「一次性提交」(如评论、搜索,无需实时监控);
  • 需要处理「文件上传」( 是天然的非受控组件,无法用状态控制);
  • 追求「性能优化」(避免频繁的状态更新和组件重渲染);
  • 简单表单(如单个输入框,无需复杂逻辑)。

3. 避坑提醒

  • 不要混合使用:同一个表单元素,不要既绑定 value 又绑定 ref,会导致逻辑混乱;
  • 非受控组件必做防御:获取值时,先判断 ref.current 是否存在,避免报错;
  • 多字段表单优先受控:用对象状态管理,代码更规范、易维护。

六、最终总结

受控组件和非受控组件没有“谁更好”,只有“谁更合适”:

✅ 受控组件是 React 表单处理的「主流方式」,符合单向数据流,适合复杂表单、需要实时控制的场景;

✅ 非受控组件更「简洁高效」,贴近原生 HTML,适合简单场景、性能敏感场景和文件上传;

记住:判断的核心是「是否需要实时控制输入值」。掌握两者的用法和区别,就能轻松应对 React 中的所有表单场景,写出简洁、高效、可维护的代码。