核心区别
受控组件 (Controlled Components)
由 React 完全控制,表单数据由 React 组件的 state 管理。
非受控组件 (Uncontrolled Components)
由 DOM 自身管理,通过 ref 获取表单值。
详细对比
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据管理 | React state | DOM |
| 值更新 | onChange事件 +setState | 用户直接输入 |
| 获取值 | state.value | ref.current.value |
| 初始化值 | value属性 | defaultValue属性 |
| 表单提交 | 从 state 获取 | 从 ref 获取 |
| 实时验证 | 容易实现 | 较难实现 |
| 即时反馈 | 立即更新 UI | 需要手动触发 |
代码示例
1.受控组件示例
import React, { useState } from 'react'; const ControlledForm = () => { const [formData, setFormData] = useState({ name: '', email: '', age: '' }); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); console.log('表单数据:', formData); }; return ( <form onSubmit={handleSubmit}> <div> <label>姓名:</label> <input type="text" name="name" value={formData.name} onChange={handleChange} /> </div> <div> <label>邮箱:</label> <input type="email" name="email" value={formData.email} onChange={handleChange} /> </div> <div> <label>年龄:</label> <input type="number" name="age" value={formData.age} onChange={handleChange} /> </div> <button type="submit">提交</button> </form> ); };2.非受控组件示例
import React, { useRef } from 'react'; const UncontrolledForm = () => { const nameRef = useRef(null); const emailRef = useRef(null); const ageRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); const formData = { name: nameRef.current.value, email: emailRef.current.value, age: ageRef.current.value }; console.log('表单数据:', formData); }; return ( <form onSubmit={handleSubmit}> <div> <label>姓名:</label> <input type="text" name="name" ref={nameRef} defaultValue="" // 初始化值 /> </div> <div> <label>邮箱:</label> <input type="email" name="email" ref={emailRef} defaultValue="" /> </div> <div> <label>年龄:</label> <input type="number" name="age" ref={ageRef} defaultValue="" /> </div> <button type="submit">提交</button> </form> ); };使用场景对比
适合使用受控组件的场景:
// 1. 需要实时验证 const ValidatedInput = () => { const [value, setValue] = useState(''); const [error, setError] = useState(''); const handleChange = (e) => { const val = e.target.value; setValue(val); // 实时验证 if (val.length < 3) { setError('至少需要3个字符'); } else { setError(''); } }; return ( <div> <input value={value} onChange={handleChange} style={{ borderColor: error ? 'red' : 'gray' }} /> {error && <div style={{ color: 'red' }}>{error}</div>} </div> ); }; // 2. 表单联动 const DependentFields = () => { const [country, setCountry] = useState(''); const [cities, setCities] = useState([]); const [selectedCity, setSelectedCity] = useState(''); const countries = { china: ['北京', '上海', '广州'], usa: ['纽约', '洛杉矶', '芝加哥'] }; const handleCountryChange = (e) => { const selected = e.target.value; setCountry(selected); setCities(countries[selected] || []); setSelectedCity(''); // 重置城市选择 }; return ( <div> <select value={country} onChange={handleCountryChange}> <option value="">选择国家</option> <option value="china">中国</option> <option value="usa">美国</option> </select> <select value={selectedCity} onChange={(e) => setSelectedCity(e.target.value)} disabled={!cities.length} > <option value="">选择城市</option> {cities.map(city => ( <option key={city} value={city}>{city}</option> ))} </select> </div> ); };适合使用非受控组件的场景:
// 1. 文件上传 const FileUpload = () => { const fileRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); console.log('文件:', fileRef.current.files[0]); }; return ( <form onSubmit={handleSubmit}> <input type="file" ref={fileRef} /> <button type="submit">上传</button> </form> ); }; // 2. 简单表单,不需要实时验证 const SimpleForm = () => { const formRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); const formData = new FormData(formRef.current); const data = Object.fromEntries(formData); console.log(data); }; return ( <form ref={formRef} onSubmit={handleSubmit}> <input name="username" defaultValue="" /> <input name="password" type="password" defaultValue="" /> <button type="submit">登录</button> </form> ); }; // 3. 集成第三方库 const ThirdPartyIntegration = () => { const editorRef = useRef(null); useEffect(() => { // 初始化第三方富文本编辑器 editorRef.current = new ThirdPartyEditor('editor'); }, []); const getContent = () => { return editorRef.current.getContent(); }; return <div id="editor"></div>; };混合使用模式
const HybridComponent = () => { const [search, setSearch] = useState(''); const fileRef = useRef(null); const handleSubmit = (e) => { e.preventDefault(); const formData = { searchTerm: search, // 来自受控输入 file: fileRef.current?.files[0] // 来自非受控输入 }; console.log('提交的数据:', formData); }; return ( <form onSubmit={handleSubmit}> {/* 受控输入:需要实时搜索建议 */} <input type="text" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="搜索..." /> {/* 非受控输入:文件上传 */} <input type="file" ref={fileRef} /> <button type="submit">提交</button> </form> ); };性能考虑
// 性能优化示例:防抖处理 const OptimizedControlledInput = () => { const [value, setValue] = useState(''); const [displayValue, setDisplayValue] = useState(''); // 防抖函数 const debouncedSetDisplayValue = useMemo( () => debounce((val) => setDisplayValue(val), 300), [] ); const handleChange = (e) => { const val = e.target.value; setValue(val); // 立即更新 state debouncedSetDisplayValue(val); // 延迟更新显示值 }; // 清理防抖函数 useEffect(() => { return () => { debouncedSetDisplayValue.cancel(); }; }, [debouncedSetDisplayValue]); return ( <div> <input value={value} onChange={handleChange} /> <p>显示的值: {displayValue}</p> </div> ); };最佳实践建议
优先使用受控组件,当:
需要实时验证和反馈
表单字段之间有依赖关系
需要动态禁用/启用字段
要实现复杂的表单逻辑
考虑使用非受控组件,当:
处理文件上传
集成第三方 DOM 库
表单非常简单,不需要实时验证
性能是关键考虑因素,且表单项非常多
React 18 的新特性
// useDeferredValue 优化受控组件性能 const DeferredInput = () => { const [value, setValue] = useState(''); const deferredValue = useDeferredValue(value); // 使用 deferredValue 进行昂贵的计算 const expensiveResult = useMemo(() => { // 昂贵的计算 return processValue(deferredValue); }, [deferredValue]); return ( <div> <input value={value} onChange={(e) => setValue(e.target.value)} /> <div>{expensiveResult}</div> </div> ); };总结
| 决策因素 | 选择受控组件 | 选择非受控组件 |
|---|---|---|
| 表单复杂度 | 高 | 低 |
| 实时验证需求 | 需要 | 不需要 |
| 性能要求 | 一般 | 极高 |
| 代码可维护性 | 高 | 低 |
| 测试友好度 | 高 | 低 |
现代 React 开发中,推荐优先使用受控组件,因为它提供更好的可预测性和可维护性。只有在有明确性能需求或特殊场景下,才考虑使用非受控组件。