在React里处理表单输入,绕不开受控组件和非受控组件这两个概念。二者最直观的差别就体现在<input>元素的value和defaultValue属性上:一个持续接管输入框的值,另一个只在初次渲染时给出默认值。理解这个差异能避免只读输入框、状态不同步以及控制台警告等问题。

一、受控组件:value与state绑定形成单一数据源
受控组件的核心思路是把表单元素的当前值交给React state管理。输入框的value属性不再是一个普通的HTML属性,而是由组件状态驱动的数据出口。用户在输入框里敲任何字符,都会触发onChange事件,事件处理函数再把最新的值写回state,于是界面上的显示始终与state保持一致。
下面是一个使用函数组件的受控输入框示例。value绑定到state变量,onChange里通过事件对象e.target.value获取用户输入。由于state更新触发重新渲染,输入框的值随之更新,这样就形成了一个完整的数据流闭环。
import { useState } from 'react';
function ControlledInput() {
const [name, setName] = useState('');
const handleChange = (e) => {
setName(e.target.value);
};
return (
<div>
<label>姓名:</label>
<input type="text" value={name} onChange={handleChange} />
<p>当前输入:{name}</p>
</div>
);
}
受控组件最大的好处是数据始终在React的掌控之中。无论是做实时校验、格式化输入、限制字符长度,还是根据输入内容动态改变按钮可用状态,都可以直接在state层面处理。例如用户输入手机号时,可以在onChange里过滤掉非数字字符,再把清洗后的值写回state。缺点也很明显:每一次按键都会触发一次setState和重新渲染,在大型表单或复杂组件树里可能带来不必要的性能开销。不过在现代React中,这种开销通常可以接受,只有极端场景需要优化。
如果只给input设置value而不提供onChange,React会将该输入框视为只读,浏览器控制台也会给出警告。这是因为受控模式要求必须提供更新state的途径,否则用户输入无法改变显示值。
二、非受控组件:defaultValue只负责初始值
非受控组件则把表单状态保留在DOM内部,React不干预用户输入过程。defaultValue属性用来设置输入框的初始值,一旦组件完成挂载,后续对defaultValue的修改不会引起DOM中已经存在的值变化。要获取用户最终输入的内容,通常需要借助ref直接读取DOM节点的value属性。
import { useRef } from 'react';
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => {
alert('提交的名字:' + inputRef.current.value);
};
return (
<div>
<label>姓名:</label>
<input type="text" defaultValue="张三" ref={inputRef} />
<button onClick={handleSubmit}>提交</button>
</div>
);
}
在上面的代码中,input的初始值为“张三”,用户修改后不会触发任何React状态更新。点击提交按钮时,通过inputRef.current.value直接读取当前DOM值。这种方式代码更简洁,不需要为每个输入项维护一个state和对应的onChange处理函数,适合表单字段很多但只在提交时才需要取值的场景。
defaultValue和value的关键区别在于:value是持续控制,defaultValue是一次性初始化。如果组件重新渲染时defaultValue发生了变化,已经渲染的输入框并不会自动更新显示值,除非组件被卸载后重新挂载。这一点在动态编辑表单时需要特别留意,很多开发者以为修改defaultValue就能重置输入框,结果发现界面上毫无变化。
三、混用value与defaultValue的常见陷阱
React明确区分受控与非受控,同一个输入框不应混用value和defaultValue。如果在JSX里同时写了value和defaultValue,React会忽略defaultValue,以value为准,并在控制台输出警告。更常见的坑是在组件生命周期内从非受控切换到受控,例如一开始使用defaultValue,后来因为业务需要改成value,或者反过来。这种切换会让React无法确定组件的状态归属,从而提示“A component is changing an uncontrolled input to be controlled”。
import { useState } from 'react';
function MixedInput() {
const [text, setText] = useState('');
// 这里根据某个条件切换受控/非受控,容易触发警告
return (
<input
type="text"
value={text}
defaultValue="初始值"
onChange={(e) => setText(e.target.value)}
/>
);
}
上面示例同时写入了value和defaultValue,React会忽略defaultValue。实际开发中如果遇到从非受控切换到受控的警告,可以先确认是否在value从undefined变成有值的过程中没有提供onChange,或者组件的受控状态在条件渲染下发生了切换。最佳实践是一个输入框从始至终只使用一种模式,避免来回切换。
对于checkbox和radio,受控属性是checked,非受控初始化属性是defaultChecked;对于select和textarea,受控使用value,非受控初始化使用defaultValue。它们的处理逻辑和input一致,只是需要注意select的value需要匹配option的value,而不是显示文本。
四、实际项目中如何选择
选择受控还是非受控,没有绝对标准,更多取决于表单的交互复杂度。如果只需要在提交时一次性收集数据,比如简单的登录表单、搜索框或文件上传,非受控组件配合ref会更轻量。如果需要实时校验、输入格式化、动态禁用按钮、多个字段联动,受控组件能显著简化逻辑,因为所有状态都集中在React中,数据流清晰可预测。
在性能敏感的场景,可以考虑使用非受控组件减少渲染次数,或者将表单拆分成更小的受控子组件来限制重渲染范围。React官方文档也提到,大多数情况下受控组件是推荐方式,但非受控组件在某些集成场景中更合适,例如需要与第三方DOM库协同工作,或者需要直接操作DOM实现复杂交互。
理解value和defaultValue背后的状态归属,就能在遇到输入框只读、状态不同步、控制台警告时快速定位问题。表单处理看似简单,但细微的属性选择会直接影响组件的可维护性和数据流设计。
受控组件非受控组件defaultValue修改时间:2026-10-02 12:09:43