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"数据驱动视图" 的设计理念,尤其在复杂表单中能显著提升可维护性。

相关推荐
ITsheng_ge2 小时前
GitHub Pages 部署静态网站流程、常见问题以及解决方案
前端·github·持续部署
web3d5202 小时前
CSS水平垂直居中终极指南:从入门到精通
前端·css
1024小神2 小时前
前端css常用的animation动画效果及其简写
前端
小白菜学前端2 小时前
Vue 配置代理
前端·javascript·vue.js
m0_zj2 小时前
63.[前端开发-Vue3]Day05-非父子通信-声明周期-refs-混合-额外补充
前端·javascript·vue.js
golang学习记3 小时前
Cursor1.7发布,AI编程的含金量还在上升!
前端
北辰alk3 小时前
Next.js 为何抛弃 Vite?自造轮子 Turbopack 的深度技术解析
前端
Cache技术分享3 小时前
203. Java 异常 - Throwable 类及其子类
前端·后端
wingring3 小时前
Vue3 后台分页写腻了?我用 1 个 Hook 删掉 90% 重复代码
前端