说说对受控组件和非受控组件的理解?应用场景?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 React 表单处理机制的理解
- 对数据流向的掌握
- 能否根据场景选择合适的组件类型
- 对 ref 的使用理解
核心概念(基础知识点)
受控组件(Controlled Component)
受控组件是指其值由 React 的 state 控制的表单元素:
- 表单数据由 React 组件管理
- 通过
value属性设置值 - 通过
onChange事件更新值 - 数据流:state -> input -> onChange -> setState -> state
非受控组件(Uncontrolled Component)
非受控组件是指其值由 DOM 自身管理的表单元素:
- 表单数据由 DOM 节点管理
- 使用
ref获取值 - 类似传统的 HTML 表单
- 可以使用
defaultValue设置初始值
详细解答(代码示例)
受控组件示例
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = { username: 'lindaidai' };
}
handleChange = (e) => {
this.setState({ username: e.target.value });
};
render() {
return (
<input
name="username"
value={this.state.username}
onChange={this.handleChange}
/>
);
}
}
关键点:
- 如果不设置
onChange,输入框将是只读的 - 每次输入都会触发 state 更新
- 组件重新渲染显示新值
完整的受控表单示例
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
email: '',
gender: 'male'
};
}
handleInputChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value });
};
handleSubmit = (e) => {
e.preventDefault();
console.log('提交的数据:', this.state);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
用户名:
<input
type="text"
name="username"
value={this.state.username}
onChange={this.handleInputChange}
/>
</label>
<br />
<label>
邮箱:
<input
type="email"
name="email"
value={this.state.email}
onChange={this.handleInputChange}
/>
</label>
<br />
<label>
性别:
<select
name="gender"
value={this.state.gender}
onChange={this.handleInputChange}
>
<option value="male">男</option>
<option value="female">女</option>
</select>
</label>
<br />
<button type="submit">提交</button>
</form>
);
}
}
非受控组件示例
import React, { Component } from 'react';
export class UnControlled extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (e) => {
e.preventDefault();
console.log('我们可以获得 input 内的值为:', this.inputRef.current.value);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input defaultValue="lindaidai" ref={this.inputRef} />
<input type="submit" value="提交" />
</form>
);
}
}
函数组件中的非受控组件
import React, { useRef } from 'react';
function UnControlledForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
alert('输入的值: ' + inputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} defaultValue="默认值" />
<button type="submit">提交</button>
</form>
);
}
深入理解(原理剖析)
受控组件的工作原理
用户输入
↓
onChange 事件触发
↓
setState 更新 state
↓
组件重新渲染
↓
input value 更新为新值
优势:
- 数据可控:所有数据都在 React 的 state 中
- 即时验证:可以在 onChange 中实时验证
- 条件禁用:可以根据条件禁用按钮
- 强制格式:可以强制特定输入格式
非受控组件的工作原理
用户输入
↓
DOM 直接更新
↓
提交时通过 ref 获取值
优势:
- 代码量少:不需要写 onChange 和 state
- 性能稍好:不需要频繁重新渲染
- 文件上传:文件输入必须是受控组件
- 集成非 React 代码:更容易与外部库集成
对比表
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据来源 | React state | DOM |
| 值获取 | this.state.xxx | ref.current.value |
| 值设置 | value 属性 | defaultValue 属性 |
| 实时验证 | 支持 | 需要额外处理 |
| 代码量 | 较多 | 较少 |
| 适用场景 | 复杂表单、需要验证 | 简单表单、快速实现 |
最佳实践
推荐使用受控组件的场景
- 需要即时验证
class ValidatedInput extends React.Component {
state = { value: '', error: '' };
handleChange = (e) => {
const value = e.target.value;
let error = '';
if (value.length < 3) {
error = '至少需要3个字符';
}
this.setState({ value, error });
};
render() {
return (
<div>
<input
value={this.state.value}
onChange={this.handleChange}
/>
{this.state.error && <span>{this.state.error}</span>}
</div>
);
}
}
- 条件提交按钮
<button disabled={!this.state.isValid}>
提交
</button>
- 动态输入格式
// 格式化电话号码
handlePhoneChange = (e) => {
const formatted = this.formatPhone(e.target.value);
this.setState({ phone: formatted });
};
推荐使用非受控组件的场景
- 简单的搜索框
function SearchForm() {
const inputRef = useRef();
const handleSearch = () => {
const query = inputRef.current.value;
// 执行搜索
};
return (
<div>
<input ref={inputRef} placeholder="搜索..." />
<button onClick={handleSearch}>搜索</button>
</div>
);
}
- 文件上传
function FileUpload() {
const fileInput = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const file = fileInput.current.files[0];
// 处理文件上传
};
return (
<form onSubmit={handleSubmit}>
<input type="file" ref={fileInput} />
<button type="submit">上传</button>
</form>
);
}
混合使用示例
class MixedForm extends React.Component {
state = {
username: '',
email: ''
};
fileInput = React.createRef();
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = (e) => {
e.preventDefault();
const formData = {
...this.state,
file: this.fileInput.current.files[0]
};
console.log(formData);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
name="username"
value={this.state.username}
onChange={this.handleChange}
/>
<input
name="email"
value={this.state.email}
onChange={this.handleChange}
/>
<input type="file" ref={this.fileInput} />
<button type="submit">提交</button>
</form>
);
}
}
面试要点
-
核心区别
- 受控:React state 管理值
- 非受控:DOM 管理值,通过 ref 获取
-
选择依据
- 需要即时反馈 -> 受控
- 简单快速实现 -> 非受控
- 文件上传 -> 非受控
- 复杂表单验证 -> 受控
-
常见误区
- 受控组件必须设置 onChange,否则无法输入
- 非受控组件使用 defaultValue,不是 value
- 不要混用受控和非受控模式
-
性能考虑
- 受控组件每次输入都会触发重新渲染
- 大型表单可以考虑使用 Formik、React Hook Form 等库
-
现代方案
- React Hook Form:基于非受控组件的高性能表单方案
- Formik:完整的表单解决方案
- 推荐使用这些库处理复杂表单
-
常见面试题
- 什么是受控组件和非受控组件?
- 什么时候使用受控组件?
- 文件上传为什么要用非受控组件?
- 如何在函数组件中实现非受控组件?