React受控组件和非受控组件的区别,用法以及常见使用场景

在 React 中,受控组件和非受控组件是处理表单数据的两种不同方式,核心区别在于表单数据的管理方式

一、核心区别

特性 受控组件(Controlled Component) 非受控组件(Uncontrolled Component)
数据来源 由 React 的state管理(单一数据源) 由 DOM 自身管理(类似原生 HTML 表单)
更新方式 通过setState()更新数据 直接操作 DOM(通过ref获取或修改值)
数据同步 表单值与state实时同步 表单值与state不同步,需主动获取
适用场景 复杂交互(实时验证、联动表单、动态禁用等) 简单场景(无需实时处理,仅提交时获取值)

二、用法示例

1. 受控组件

表单元素的值由 React 的state控制,用户输入会触发事件处理函数(如onChange),通过setState更新state,进而更新表单值。

jsx 复制代码
import { useState } from 'react';

function ControlledForm() {
  // 用state管理表单数据
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');

  // 处理表单提交
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交数据:', { username, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 用户名:value绑定state,onChange更新state */}
      <input
        type="text"
        value={username} // 受控于state
        onChange={(e) => setUsername(e.target.value)} // 输入时更新state
        placeholder="用户名"
      />

      {/* 邮箱:同理 */}
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="邮箱"
      />

      <button type="submit">提交</button>
    </form>
  );
}

特点

  • 表单值完全受 React 控制,可实时监控输入变化(如实时验证)。
  • 必须配合onChange(或其他事件)更新state,否则输入会被 "锁定"(无法修改)。

2. 非受控组件

表单数据由 DOM 自身管理,通过ref直接访问 DOM 元素获取值,初始值可通过defaultValue(或defaultChecked for 复选框)设置。

jsx 复制代码
import { useRef } from 'react';

function UncontrolledForm() {
  // 创建ref关联DOM元素
  const usernameRef = useRef(null);
  const emailRef = useRef(null);

  // 处理表单提交
  const handleSubmit = (e) => {
    e.preventDefault();
    // 通过ref获取DOM值
    const username = usernameRef.current.value;
    const email = emailRef.current.value;
    console.log('提交数据:', { username, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 用户名:ref关联DOM,defaultValue设置初始值 */}
      <input
        type="text"
        ref={usernameRef} // 关联ref
        defaultValue="默认用户名" // 仅初始渲染生效
        placeholder="用户名"
      />

      {/* 邮箱:同理 */}
      <input
        type="email"
        ref={emailRef}
        defaultValue=""
        placeholder="邮箱"
      />

      <button type="submit">提交</button>
    </form>
  );
}

特点

  • 更接近原生 HTML 表单行为,代码更简洁。
  • 无法实时监控输入变化,适合简单场景(如一次性提交)。
  • 文件上传(input type="file")只能用非受控组件(因安全限制,无法通过value控制)。

三、受控组件的典型应用场景

受控组件适合需要实时处理表单数据 的场景,如实时验证、动态交互、数据联动等。其核心优势是能通过 state 实时掌控表单状态,便于实现复杂逻辑。

场景 1:实时验证的注册表单

注册表单中,通常需要实时检查用户名是否已存在、密码强度、邮箱格式等。这类场景必须使用受控组件,因为需要在用户输入过程中即时反馈验证结果。

jsx 复制代码
import { useState } from 'react';

function RegisterForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
    email: ''
  });
  const [errors, setErrors] = useState({});

  // 实时验证邮箱格式
  const validateEmail = (email) => {
    const regex = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
    return regex.test(email) ? '' : '邮箱格式不正确';
  };

  // 输入变化时更新state并验证
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));

    // 实时验证(仅示例邮箱)
    if (name === 'email') {
      setErrors(prev => ({ ...prev, email: validateEmail(value) }));
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // 提交前全量验证...
    console.log('提交注册数据:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="用户名"
      />
      
      <input
        name="email"
        type="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="邮箱"
      />
      {errors.email && <span style={{ color: 'red' }}>{errors.email}</span>} {/* 实时显示错误 */}
      
      <input
        name="password"
        type="password"
        value={formData.password}
        onChange={handleChange}
        placeholder="密码"
      />
      
      <button type="submit">注册</button>
    </form>
  );
}

为什么用受控组件?

  • 需要在用户输入时即时验证并显示错误信息(依赖实时获取输入值)。
  • 可能需要根据输入内容动态调整表单(如密码输入到一定长度才显示 "强度提示")。

场景 2:搜索框的实时联想提示

搜索框在用户输入过程中,需要实时请求后端接口并展示联想结果(如百度搜索的下拉提示)。这类场景必须用受控组件,因为需要监听每一次输入变化并触发后续逻辑。

jsx 复制代码
import { useState, useEffect } from 'react';

function SearchBox() {
  const [searchText, setSearchText] = useState('');
  const [suggestions, setSuggestions] = useState([]);

  // 输入变化时更新state,并请求联想接口
  useEffect(() => {
    if (searchText.length < 2) {
      setSuggestions([]);
      return;
    }

    // 模拟接口请求:每输入一个字符就触发一次
    const timer = setTimeout(() => {
      fetch(`/api/suggest?keyword=${searchText}`)
        .then(res => res.json())
        .then(data => setSuggestions(data));
    }, 300); // 防抖处理

    return () => clearTimeout(timer);
  }, [searchText]); // 依赖searchText,输入变化时重新执行

  return (
    <div>
      <input
        type="text"
        value={searchText}
        onChange={(e) => setSearchText(e.target.value)}
        placeholder="搜索..."
      />
      <ul>
        {suggestions.map((item) => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    </div>
  );
}

为什么用受控组件?

  • 必须实时获取用户输入的searchText,才能触发联想接口请求。
  • 联想结果的展示状态(suggestions)与输入值强关联,需通过state同步管理。

四、非受控组件的典型应用场景

非受控组件适合无需实时处理数据 的简单场景,仅在提交 / 操作时获取值即可。其优势是代码更简洁,减少state和事件处理的冗余。

场景 1:简单的登录弹窗

登录弹窗通常只需要在用户点击 "登录" 按钮时,一次性获取用户名和密码,无需实时验证(或仅在提交时验证)。这类场景用非受控组件更简洁。

jsx 复制代码
import { useRef } from 'react';

function LoginModal() {
  // 用ref关联DOM元素
  const usernameRef = useRef(null);
  const passwordRef = useRef(null);

  const handleLogin = () => {
    // 仅在点击登录时获取值
    const username = usernameRef.current.value;
    const password = passwordRef.current.value;
    
    // 提交验证
    if (!username || !password) {
      alert('请输入用户名和密码');
      return;
    }
    console.log('登录请求:', { username, password });
  };

  return (
    <div className="modal">
      <input
        ref={usernameRef}
        type="text"
        defaultValue=""
        placeholder="用户名"
      />
      <input
        ref={passwordRef}
        type="password"
        defaultValue=""
        placeholder="密码"
      />
      <button onClick={handleLogin}>登录</button>
    </div>
  );
}

为什么用非受控组件?

  • 无需实时处理输入(如输入过程中不验证),仅在提交时获取值即可。
  • 减少stateonChange的定义,代码更简洁。

场景 2:文件上传功能

由于浏览器安全限制,<input type="file"> 的值无法通过 JavaScript 设置(value属性只读),只能通过ref获取用户选择的文件。因此,文件上传必须使用非受控组件。

jsx 复制代码
import { useRef } from 'react';

function FileUploader() {
  const fileInputRef = useRef(null);

  const handleUpload = () => {
    // 通过ref获取用户选择的文件
    const selectedFile = fileInputRef.current.files[0];
    if (!selectedFile) {
      alert('请选择文件');
      return;
    }
    console.log('上传文件:', selectedFile);
    
    // 模拟文件上传(实际中会用FormData)
    const formData = new FormData();
    formData.append('file', selectedFile);
    // fetch('/api/upload', { method: 'POST', body: formData });
  };

  return (
    <div>
      {/* 文件输入必须用非受控组件 */}
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*"
      />
      <button onClick={handleUpload}>上传</button>
    </div>
  );
}

为什么用非受控组件?

  • 浏览器限制:input[type="file"]value无法通过state设置,只能通过ref访问用户选择的文件。
  • 无需实时处理,仅在用户点击 "上传" 时获取文件即可。

五、如何选择?

  • 受控组件 :优先用于需要实时处理数据 的场景(验证、联动、实时反馈等),核心是通过state掌控数据流向。
  • 非受控组件 :适合简单交互 场景(仅提交时获取值)或浏览器限制场景 (如文件上传),核心是通过ref直接操作 DOM。

实际开发中,受控组件的使用频率更高,因为它更符合 React"数据驱动视图" 的设计理念,尤其在复杂表单中能显著提升可维护性。

相关推荐
weixin_382395232 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__2 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.3 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~4 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华5 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子7 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅7 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni7 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学9 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端