一、React非受控组件的核心概念1.1 什么是受控组件在 React 中受控组件是指表单元素的值由 React 的 state 完全控制。用户每次输入都会触发 onChange 事件React 接收事件后更新 state再由 state 驱动 UI 重新渲染使 input 的 value 与 state 保持同步。在这种模式下React state 就是表单数据的单一数据源数据的流向始终是单向的。受控组件的核心特征是input 的 value 绑定到 stateonChange 回调负责更新 state。这种方式让 React 对表单数据拥有完全的控制权便于做实时校验、格式化、条件渲染等操作。import { useState } from react; function ControlledInput() { const [value, setValue] useState(); return ( input value{value} onChange{(e) setValue(e.target.value)} / ); }1.2 什么是非受控组件非受控组件是指表单元素的值由 DOM 自身管理React 不通过 state 实时跟踪输入值。当需要获取表单数据时React 通过 ref 直接访问 DOM 节点并读取其 value 属性。这种方式更接近传统 HTML 表单的处理模式表单数据保存在 DOM 中React 只在需要时去取。非受控组件的核心特征是不绑定 value 到 state通过 ref 引用 DOM 节点在提交或需要时再读取值。由于 React 不参与每次输入的状态更新因此不会因每次按键而触发重新渲染。import { useRef } from react; function UncontrolledInput() { const inputRef useRef(null); const handleSubmit (e) { e.preventDefault(); console.log(输入的值是:, inputRef.current.value); }; return ( form onSubmit{handleSubmit} input ref{inputRef} defaultValue / button typesubmit提交/button /form ); }1.3 受控组件与非受控组件的对比理解二者的差异有助于在实际开发中做出正确选择。以下是两种模式的核心对比| 对比维度 | 受控组件 | 非受控组件 ||---------|---------|-----------|| 数据源 | React state | DOM || 值的获取 | 通过 state 读取 | 通过 ref 读取 || 每次输入是否重新渲染 | 是 | 否 || 实时校验 | 容易实现 | 较难实现 || 代码复杂度 | 较高 | 较低 || 适用场景 | 复杂表单、实时校验 | 简单表单、一次性提交 |下面用流程图直观展示两种组件的数据流向差异受控组件非受控组件用户在输入框输入组件类型触发 onChange 事件调用 setState 更新 stateReact 重新渲染组件input 的 value 被 state 更新DOM 直接更新输入值React 不感知变化需要时通过 ref.current.value 读取获取最终值从图中可以看出受控组件的数据流形成了一个闭环输入触发 state 更新state 更新驱动 UI 变化而非受控组件的数据流是单向的DOM 自行管理值React 只在需要时通过 ref 读取。二、非受控组件的使用方法与应用场景2.1 使用 useRef 创建引用useRef 是 React Hook 中用于创建 ref 引用的 API它返回一个可变的 ref 对象其 .current 属性被初始化为传入的参数。在非受控组件中我们将 ref 对象赋值给 input 的 ref 属性React 会在组件挂载后自动将 DOM 节点赋值到 ref.current 上。import { useRef } from react; function NameForm() { const nameRef useRef(null); const handleClick () { alert(姓名: nameRef.current.value); }; return ( div input typetext ref{nameRef} placeholder请输入姓名 / button onClick{handleClick}获取值/button /div ); }使用 useRef 时需要注意修改 ref.current 不会触发组件重新渲染这与 setState 有本质区别。ref 更像是组件的一个盒子用于存放跨渲染周期需要保留的值。2.2 设置默认值在非受控组件中不能使用 value 属性设置初始值这会让 input 变成只读而应该使用 defaultValue 属性。对于 checkbox 和 radio使用 defaultChecked对于 select同样使用 defaultValue。function DefaultValueForm() { const inputRef useRef(null); return ( form input defaultValue默认值 ref{inputRef} / input typecheckbox defaultChecked{true} / select defaultValuebanana option valueapple苹果/option option valuebanana香蕉/option option valueorange橘子/option /select /form ); }2.3 处理文件输入文件输入是典型的必须使用非受控组件的场景。在 React 中input typefile /始终是非受控组件因为它的值是只读的无法通过 state 控制。用户选择文件后通过 ref 可以访问到 FileList 对象。function FileInput() { const fileRef useRef(null); const handleSubmit (e) { e.preventDefault(); const files fileRef.current.files; if (files.length 0) { console.log(文件名:, files[0].name); console.log(文件大小:, files[0].size, 字节); console.log(文件类型:, files[0].type); } }; return ( form onSubmit{handleSubmit} input typefile ref{fileRef} / button typesubmit上传/button /form ); }2.4 一次性表单提交场景当表单只在提交时才需要获取所有字段的值而不需要实时跟踪每个字段的输入状态时非受控组件是更好的选择。这种方式避免了为每个字段维护 state减少了不必要的渲染和代码量。function OneTimeForm() { const usernameRef useRef(null); const emailRef useRef(null); const messageRef useRef(null); const handleSubmit (e) { e.preventDefault(); const formData { username: usernameRef.current.value, email: emailRef.current.value, message: messageRef.current.value }; console.log(表单数据:, formData); }; return ( form onSubmit{handleSubmit} div label用户名: /label input ref{usernameRef} / /div div label邮箱: /label input typeemail ref{emailRef} / /div div label留言: /label textarea ref{messageRef} / /div button typesubmit提交/button /form ); }如果用受控组件实现同样的功能需要为三个字段分别维护 state每个 input 都绑定 value 和 onChange代码量明显增加且每次输入都会触发重新渲染。2.5 集成非 React 代码场景当需要将 React 与非 React 代码如 jQuery 插件、第三方库集成时非受控组件可以避免 React 与外部代码对同一 DOM 节点的控制冲突。React 只负责渲染外部库负责操作 DOM二者各司其职。function RichEditor() { const editorRef useRef(null); useEffect(() { // 假设使用第三方富文本编辑器 const editor new SomeEditor(editorRef.current); editor.init(); return () editor.destroy(); }, []); return div ref{editorRef} /; }2.6 性能敏感的输入场景对于某些性能敏感的场景如大型表单中包含大量输入框使用受控组件会导致每次输入都触发整个表单的重新渲染。此时可以考虑使用非受控组件避免不必要的渲染开销。三、最佳实践与注意事项3.1 何时选择非受控组件在实际开发中选择非受控组件的判断依据包括以下几点表单只在提交时需要数据无需实时跟踪输入值。文件上传场景。集成第三方 DOM 操作库。表单字段非常多且不需要实时校验。需要保留浏览器原生的表单行为如自动填充、历史记录。下面是一个决策流程图是否是否是否是否开始选择组件类型需要实时校验或格式化?选择受控组件是否为文件输入?必须用非受控组件是否只需提交时获取值?选择非受控组件是否集成第三方库?3.2 常见陷阱与解决方案在使用非受控组件时有几个常见陷阱需要注意误用 value 属性。如果在非受控组件中使用 value 属性而不提供 onChangeReact 会发出警告input 会变成只读。正确做法是使用 defaultValue。// 错误写法 input valuehello ref{inputRef} / // 正确写法 input defaultValuehello ref{inputRef} /ref 为 null 的处理。在组件首次渲染时ref.current 可能是 null直接访问 .value 会报错。应在事件回调中访问确保 DOM 已挂载。无法实时响应输入变化。由于非受控组件不跟踪每次输入如果需要在输入时执行某些逻辑如按钮禁用状态切换需要改为受控组件或使用其他方案。3.3 与受控组件的混合使用在实际项目中非受控组件和受控组件并非互斥关系可以在同一个表单中混合使用。通常的做法是对需要实时校验或联动控制的字段使用受控组件对简单的一次性提交字段使用非受控组件。function MixedForm() { const [username, setUsername] useState(); const bioRef useRef(null); const handleSubmit (e) { e.preventDefault(); console.log({ username, bio: bioRef.current.value }); }; return ( form onSubmit{handleSubmit} input value{username} onChange{(e) setUsername(e.target.value)} placeholder用户名(受控) / textarea ref{bioRef} placeholder个人简介(非受控) / button typesubmit提交/button /form ); }这种混合模式既保留了受控组件的实时控制能力又利用了非受控组件的简洁性是实际开发中推荐的做法。总结来说非受控组件是 React 表单处理的重要补充它通过 ref 直接读取 DOM 值避免了 state 管理的复杂性特别适合简单表单、文件上传和第三方集成等场景。理解受控与非受控的差异根据实际需求灵活选择才能写出既简洁又高效的 React 代码。