创见博客
受控组件与非受控组件
七崽爱吃小饼干2026/03/04阅读 2专栏 React

React 受控组件 & 非受控组件

一、核心前置概念:React 的「单向数据流」

React 是单向数据流的框架:数据(state/props)的流向是「自上而下」的,只能从父组件传递给子组件,数据变更只会触发 UI 重新渲染,表单元素本身的原生数据(比如 input 的 value)默认和 React 的数据是隔离的。 这是理解受控/非受控组件的核心前提,两种组件的本质,就是「React 要不要接管表单元素的原生数据控制权」。

二、一、受控组件(Controlled Component)

定义

React 组件通过 state(或父组件的 props)完全接管表单元素的「值」和「值更新」,表单元素自身不再维护原生的内部状态,它的任何值变化都由 React 来驱动和管理。

核心特征

  1. 表单元素的 value 属性 被 React 的 state/props 绑定(赋值),元素展示的值 = React 数据的值;
  2. 表单元素绑定 onChange 事件(值变更回调),当表单值发生变化时,立刻通过 setState 更新 React 的 state;
  3. 表单元素的「值」完全受 React 控制,表单的一切值变更行为,都必须经过 React 的数据更新流程。

核心工作流程

用户输入 → 触发表单元素 onChange 事件 → 调用 setState 更新 React 中的状态 → 状态更新触发组件重新渲染 → 表单元素的 value 被重新赋值为最新状态 → 页面展示最新值。

完整可运行示例(最常用的 input 受控组件)

jsx
import { useState } from 'react';

// 受控组件示例
const ControlledInput = () => {
  // 1. 用 state 存储表单值,作为唯一数据源
  const [inputValue, setInputValue] = useState('');

  // 2. 绑定 onChange 事件,监听值变化并更新 state
  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

  // 3. 绑定表单提交事件,获取的是 state 中的值(可靠)
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交的内容:', inputValue);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 核心:value 绑定 state,onChange 绑定更新函数 */}
      <input 
        type="text" 
        value={inputValue} 
        onChange={handleChange} 
        placeholder="受控输入框"
      />
      <button type="submit">提交</button>
    </form>
  );
};

export default ControlledInput;

三、二、非受控组件(Uncontrolled Component)

定义

表单元素的「值」由自身的原生 DOM 节点维护,React 不接管它的状态,组件渲染时,表单元素用自己的默认值展示,后续的输入变化也不会同步到 React 的 state 中。

核心特征

  1. 表单元素不绑定 value 属性,而是用 defaultValue 属性 设置「初始默认值」(只在组件首次渲染时生效,后续不会被 React 更新);
  2. 不绑定 onChange 事件,不需要实时更新 React 的 state;
  3. React 想要获取表单的值时,需要通过 ref 手动获取原生 DOM 节点,从 DOM 上读取当前值;
  4. 表单的「值」不受 React 控制,由浏览器原生 DOM 管理,是「非受控」的核心体现。

完整可运行示例(最常用的 input 非受控组件)

jsx
import { useRef, useEffect } from 'react';

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

  useEffect(() => {
    // 模拟获取数据
    setTimeout(() => {
      // 直接操作 DOM 赋值(唯一有效方式)
      inputRef.current.value = '我是初始内容';
    }, 1000);
  }, []);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交:', inputRef.current.value);
  };

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

export default UncontrolledInput;

四、关键注意点

1. 不要给非受控组件绑定 value 属性!

如果给非受控组件(用 defaultValue+ref 的表单)绑定了 value,React 会报一个明确的警告:

Warning: You provided a value prop to a form field without an onChange handler. This will render a read-only field.

原因:绑定 value 意味着「React 要控制值」,但又没有 onChange 更新值,最终会导致表单元素只读,用户无法输入任何内容,这是新手最常见的错误!

2. 补充:复选框/单选框的特殊属性

  • 普通输入框/文本域:受控用 value+onChange,非受控用 defaultValue+ref
  • 复选框(checkbox)/单选框(radio):受控用 checked+onChange,非受控用 defaultChecked+ref 本质逻辑不变,只是属性名不同,都是「受控绑定『当前值』,非受控绑定『默认值』」。

五、 受控 vs 非受控 核心区别

这是最核心的考点,建议熟记,表格中把所有关键差异都整理好了:

对比维度受控组件 (Controlled)非受控组件 (Uncontrolled)
数据控制权由 React 的 state/props 完全控制由 DOM 原生节点自身维护
表单值绑定绑定 value / checked 属性绑定 defaultValue / defaultChecked 属性
值更新方式必须绑定 onChange 事件,实时更新 state无需绑定 onChange,无实时数据更新
值获取方式直接从 React 的 state 中读取(可靠)通过 ref 获取原生 DOM 节点的 value
数据一致性表单值 ≡ state 值,始终一致表单值是 DOM 原生值,和 React 数据无关联
实时校验能力支持(输入时实时触发 onChange,可做校验)不支持(只能在提交时获取值后校验)
灵活性高(可实时格式化、禁用、联动表单)低(只能做简单的表单提交)

六、选型原则:什么时候用受控?什么时候用非受控?

优先用「受控组件」的场景(90% 的业务场景)

这是 React 官方和行业的主流推荐方案,满足绝大多数业务需求,推荐优先使用:

  1. 需要实时处理用户输入:比如实时表单校验(输入时提示手机号格式错误)、实时格式化(输入金额自动加千分位)、输入防抖搜索;
  2. 需要表单联动:比如「选择省份后,联动更新城市下拉框」、「勾选同意协议后,才能点击提交按钮」;
  3. 需要禁用/重置表单:比如根据状态禁用输入框、点击「重置」按钮一键清空表单值(直接更新 state 即可);
  4. 需要精准控制表单值:比如限制输入长度、只能输入数字、密码明文/密文切换;
  5. 复杂表单场景:多表单项联动、分步表单、表单提交前需要做数据预处理。

适合用「非受控组件」的场景(10% 的简单场景)

非受控组件不是「不好」,而是更适合简单场景,代码更简洁,无需维护多余的 state 和 onChange,是「够用就好」的选择:

  1. 简单的表单提交:只需要在用户点击提交时获取一次值,不需要实时处理输入(比如登录页的用户名密码、简单的留言板);
  2. 文件上传控件 (<input type="file" />):✅ 这是唯一必须用非受控组件的场景!因为文件上传控件的 value 是只读的,浏览器不允许 JS 手动修改文件路径,只能通过 ref 获取原生 DOM 的 files 属性,没有任何受控的写法;
  3. 不需要实时校验/格式化的表单:比如简单的备注输入框、昵称输入框,只需要提交时拿到值即可;
  4. 快速开发的简单场景:追求代码简洁,不想写多余的 state 和事件处理函数。

总结

  1. 受控组件:React 管值,value/checked + onChange + state,数据和视图强绑定,功能强大,是主流方案;
  2. 非受控组件:DOM 管值,defaultValue/defaultChecked + ref,无需维护状态,代码简洁,适合简单场景;
  3. 核心区别:谁掌握表单值的「控制权」 —— React 控制就是受控,DOM 控制就是非受控;
  4. 避坑核心:非受控组件不能绑 value,文件上传只能用非受控;
  5. 选型口诀:复杂场景用受控,简单场景用非受控,文件上传必用非受控。
评论
0/100