面试官:说说 React 中受控组件与非受控组件的区别?
在 React 开发中,处理表单元素是家常便饭。而在面试中,关于“受控组件(Controlled Components)”与“非受控组件(Uncontrolled Components)”的区别,往往是考察候选人对 React 数据流理解深度的经典问题。
简单来说,这二者的核心区别在于:“谁拥有数据的决定权?”
1. 受控组件:React 是“唯一的真理之源”
概念解析:
受控组件意味着表单元素的值(value)完全由 React 的 state(状态)来控制。DOM 元素本身并不维护状态,它只是 React 状态的一个“镜像”。用户输入时,必须通过 onChange 事件回调来更新状态,从而实现数据的双向绑定。
面试话术: “受控组件遵循单向数据流原则。React 拥有数据的最高控制权,输入框的显示值完全取决于 state。这种模式让数据处理非常直观,因为所有的变更都在 React 的掌控之中。”
代码示例分析:
以下是一个典型的受控组件登录表单。value 被绑定到了 form 状态,任何输入都会触发 handleChange 来同步更新状态。
import { useState } from 'react';
export default function LoginForm() {
// 状态完全由 React 控制
const [form, setForm] = useState({
username: '',
password: ''
});
const handleChange = (e) => {
// 每次输入都实时更新 React 状态
setForm({
...form,
[e.target.name]: e.target.value
})
}
const handleSubmit = (e) => {
e.preventDefault();
console.log(form); // 提交时直接获取最新的 React 状态
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
// 值受 state 控制,onChange 受 React 监听
value={form.username}
onChange={handleChange}
/>
<input
type="password"
name="password"
value={form.password}
onChange={handleChange}
/>
<button type="submit">注册</button>
</form>
)
}
2. 非受控组件:DOM 是“真理之源”
概念解析:
非受控组件是指表单元素的值由 DOM 自身来维护,而不是通过 React 的 state。React 仅在需要的时候(例如表单提交时)通过 ref 去“查询” DOM 获取值,而不是实时监听。
面试话术:
“非受控组件更像是传统的 HTML 表单。它的值由原生 DOM 管理,React 仅作为‘旁观者’。我们通常使用 useRef 来直接引用 DOM 节点,从而在特定时刻读取其值。”
代码示例分析:
在这个评论框组件中,我们没有使用 onChange 来监听每一次键盘敲击,而是在点击提交按钮时,直接通过 textareaRef.current.value 从 DOM 中取出值。
import { useRef } from "react";
export default function CommentBox() {
// 创建一个 ref 来直接引用 DOM 节点
const textareaRef = useRef(null);
const handleSubmit = () => {
// 直接从 DOM 中获取值,而不是从 state 中
const comment = textareaRef.current.value;
if(!comment) return alert('请输入评论');
console.log(comment);
}
return (
<div>
{/* 没有 value 和 onChange,仅通过 ref 绑定 */}
<textarea
ref={textareaRef}
placeholder="输入评论..."
></textarea>
<button onClick={handleSubmit}>提交</button>
</div>
)
}
3. 深度对比:如何在面试中展示你的技术选型能力?
当面试官问“什么时候用哪个”时,你需要展示出对业务场景的权衡能力。
| 维度 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据流向 | 单向数据流 (State -> View -> State) | 直接操作 DOM |
| 性能 | 频繁触发 setState 可能导致重渲染,开销较大 | 仅在需要时读取,性能更优 |
| 代码复杂度 | 逻辑清晰,但代码量稍多(需写 handler) | 代码简洁,接近原生 JS 逻辑 |
| 适用场景 | 实时验证、动态表单、数据联动、复杂交互 | 文件上传、简单的搜索框、集成非 React 库 |
4. 总结
在面试的最后,你可以这样总结来拔高回答:
- 受控组件是 React 的“首选模式”。它将数据和 UI 同步,便于实现即时反馈(如输入限制、表单验证),非常适合需要高交互性的复杂表单系统。
- 非受控组件则是一种“性能妥协”或“简单场景下的快捷方式”。当表单仅用于一次性提交,或者为了优化大量输入框的性能时,使用
ref直接操作 DOM 是一个非常有效的策略。
掌握这两者的区别,不仅能帮你写出更高效的代码,还能让你在面对不同业务需求时做出更合理的技术选型。
5. 关键补充:onChange 事件在双向绑定中的作用
在讨论受控组件时,onChange 事件是实现“双向绑定”幻觉的关键一环。理解它的行为对于避免常见的陷阱至关重要。
onChange 的工作原理:
在 React 中,onChange 是一个合成事件(SyntheticEvent),它是对原生 DOM 事件的跨浏览器包装。对于 <input>、<textarea> 等元素,React 的 onChange 事件行为更接近于原生的 input 事件,即在值每次改变时立即触发,而不是像原生 change 事件那样需要等到元素失去焦点时才触发。
为什么 onChange 不可或缺?
在受控组件中,如果你只设置了 value 属性而没有提供 onChange 处理函数,输入框将会变成只读的。这是因为 value 属性将输入框的值锁定到了 React 的状态,而缺少 onChange 意味着没有机制来更新这个状态。用户输入时,状态不会改变,React 重新渲染时输入框的值依然保持原样,从而表现为无法输入。
因此,onChange 是连接用户输入(视图层)和 React 状态(数据层)的桥梁,它确保了数据的双向流动。
onChange 事件对象(e)的使用:
onChange 处理函数会接收一个事件对象 e。通过 e.target,你可以访问到触发事件的 DOM 元素,并从中获取最新的值。
- 对于文本输入框(
<input type="text">)、文本域(<textarea>)等,使用e.target.value获取用户输入的最新字符串。 - 对于复选框(
<input type="checkbox">)和单选按钮(<input type="radio">),使用e.target.checked获取其是否被选中的布尔值。
const handleChange = (e) => {
// 对于文本输入
const textValue = e.target.value;
// 对于复选框
const isChecked = e.target.checked;
// ... 更新状态
};
正确理解和使用 onChange 事件,是构建响应式和用户友好型表单的基础。