类组件和函数组件的区别是什么?
一、 代码实现
- 类组件
class ClassComponent extends React.Component {
state = { count: 0 };
increment = () => this.setState({ count: this.state.count + 1 });
render() {
return <button onClick={this.increment}>{this.state.count}</button>;
}
}
- 函数组件
function FunctionComponent() {
const [count, setCount] = React.useState(0);
const increment = () => setCount(count + 1);
return <button onClick={increment}>{count}</button>;
}
二、 调用机制
- 类组件
- React 通过
new ClassComponent() 创建实例
- 实例保存在 Fiber 节点的
stateNode 属性中
- 每次更新时调用实例的
render() 方法
- 生命周期方法通过实例直接调用
<ClassComponent />
- 函数组件
- React 直接调用函数
FunctionComponent(props)
- 无实例创建,状态存储在 Fiber 节点的
memoizedState 中
- Hooks 通过链表结构按顺序存储
- 每次渲染都是全新函数调用(但状态被保留)
<FunctionComponent />
三、 生命周期
- 类组件
class LifecycleDemo extends React.Component {
constructor(props) {
super(props);
console.log("Constructor called");
}
componentDidMount() {
console.log("Component did mount");
}
componentDidUpdate() {
console.log("Component did update");
}
componentWillUnmount() {
console.log("Component will unmount");
}
render() {
console.log("Render called");
return <div>Lifecycle Demo</div>;
}
}
- 函数组件
function LifecycleDemo() {
console.log("Function body executed");
React.useEffect(() => {
console.log("Component did mount (useEffect empty dep)");
return () => {
console.log("Component will unmount");
};
}, []);
React.useEffect(() => {
console.log("Component did update (useEffect no dep)");
});
return <div>Lifecycle Demo</div>;
}
生命周期对应关系
| 类组件生命周期 | 函数组件等价实现 | 触发时机 |
|---|
| constructor | useState初始化 | 组件创建时 |
| componentDidMount | useEffect(..., []) | DOM挂载后 |
| componentDidUpdate | useEffect(..., [deps]) | 依赖项变化后 |
| componentWillUnmount | useEffect清理函数 | 组件卸载前 |
| shouldComponentUpdate | React.memo + useMemo | 渲染前决定是否更新 |
四、 状态管理
- 类组件
class ClassUpdate extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
};
incrementTwice = () => {
this.setState(prev => ({ count: prev.count + 1 }));
this.setState(prev => ({ count: prev.count + 1 }));
};
}
- 函数组件
function FunctionUpdate() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
};
const incrementTwice = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
}
五、 Props获取
- 类组件
- 通过
this.props访问
- 父组件重新渲染时,React 会更新子组件实例的
this.props
- 整个组件生命周期中,同一个实例的
this.props 会被更新
class ClassProps extends React.Component {
render() {
return <div>{this.props.message}</div>;
}
}
- 函数组件
- 通过函数参数直接访问或解构访问
- 函数内部捕获的是当次渲染时的 props 值
- props 变化会导致函数组件重新执行
function FunctionProps(props) {
return <div>{props.message}</div>;
}
function FunctionPropsDestructured({ message }) {
return <div>{message}</div>;
}
函数组件的闭包陷阱
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<button onClick={increment}>Increment</button>
<span>Count: {count}</span>
</div>
);
}
function Parent() {
const [start, setStart] = useState(0);
return (
<div>
<button onClick={() => setStart(10)}>设置为10</button>
<Counter initialCount={start} />
</div>
);
}
- 当点击 "设置为10" 后,Counter 组件的
initialCount 变为 10
- 但 increment 函数仍然使用闭包中捕获的旧值(0)
- 导致点击 Increment 按钮从 0 开始计数
- 解决方法
- 函数式更新
const increment = () => {
setCount(c => c + 1);
};
- 使用
ref保存最新值
const latestInitial = useRef(initialCount);
useEffect(() => {
latestInitial.current = initialCount;
}, [initialCount]);
const increment = () => {
setCount(latestInitial.current + 1);
};

受控组件和非受控组件的区别是什么?
一、实现方式
- 受控组件(React完全控制数据)
- 创建合成事件对象(SyntheticEvent)
- 调用事件处理函数
- 调度状态更新
- 重新渲染组件树
- 协调器(Reconciler)比较虚拟DOM
- 渲染器(Renderer)更新实际DOM
function ControlledInput() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
return (
<input
// 新值传递到DOM
value={value}
onChange={handleChange}
/>
);
}
2. 非受控组件(DOM拥有数据)
- 初始渲染时设置
defaultValue
- 创建DOM节点引用
- 用户输入直接修改DOM
- React完全不知道变化发生
- 需要时通过ref访问DOM节点值
function UncontrolledInput() {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<input
// React不管理值
defaultValue="initial"
ref={inputRef}
/>
);
}

二、优化策略
- 受控组件
- 防抖处理
- 问题背景:搜索框等高频输入场景导致过多API请求
- 优化原理:防抖减少API调用频率,使用ref保持函数稳定性
function DebouncedSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const debouncedSearch = useRef(
_.debounce(searchTerm => {
fetchResults(searchTerm).then(setResults);
}, 300)
).current;
useEffect(() => {
debouncedSearch(query);
return () => debouncedSearch.cancel();
}, [query, debouncedSearch]);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search..."
/>
<SearchResults results={results} />
</div>
);
}
- 批量更新
- 问题背景:在大型表单中,每次输入都会触发完整的状态更新和重新渲染,导致性能瓶颈
- 优化原理:使用函数式更新确保基于最新状态,避免直接修改状态对象,减少渲染次数
function BatchUpdateForm() {
const [form, setForm] = useState({
firstName: '',
lastName: '',
email: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({
...prev,
[name]: value
}));
};
return (
<form>
<input name="firstName" value={form.firstName} onChange={handleChange} />
<input name="lastName" value={form.lastName} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
</form>
);
}
- Web Workers处理复杂计算
- 问题背景:受控组件的状态更新会引发重新渲染,若同步执行复杂计算(如大数据排序/过滤),会导致界面卡顿;用户输入(如搜索框)需实时反馈,但计算可能耗时。Web Workers确保输入事件不被阻塞,维持流畅交互
- 优化原理:将计算逻辑移至Web Worker,后台线程完成计算后通过
postMessage返回结果,主线程仅负责UI更新
function ComplexCalculationForm() {
const [input, setInput] = useState('');
const [result, setResult] = useState(null);
const workerRef = useRef();
useEffect(() => {
workerRef.current = new Worker('./calculation.worker.js');
workerRef.current.onmessage = (e) => {
setResult(e.data);
};
return () => {
workerRef.current.terminate();
};
}, []);
const handleChange = (e) => {
const value = e.target.value;
setInput(value);
workerRef.current.postMessage(value);
};
return (
<div>
<input value={input} onChange={handleChange} />
{result && <div>Result: {result}</div>}
</div>
);
}
- 非受控组件
- 部分更新策略
- 问题背景:超大型表单中提交所有数据性能低下
- 优化原理:仅收集修改过的字段数据
function LargeFormOptimization() {
const formRef = useRef();
const modifiedFields = useRef(new Set());
const handleChange = (e) => {
modifiedFields.current.add(e.target.name);
};
const handleSubmit = () => {
const formData = new FormData(formRef.current);
const data = {};
modifiedFields.current.forEach(name => {
data[name] = formData.get(name);
});
console.log('Modified data:', data);
};
return (
<form ref={formRef}>
{Array.from({ length: 1000 }).map((_, i) => (
<input
key={i}
name={`field-${i}`}
defaultValue=""
onChange={handleChange}
/>
))}
<button type="button" onClick={handleSubmit}>Submit</button>
</form>
);
}
- Ref回调优化
- 问题背景:动态生成的表单字段难以管理ref
- 优化原理:使用回调ref动态管理字段引用
function DynamicForm() {
const inputsRef = useRef({});
const registerInput = useCallback((name) => (element) => {
if (element) {
inputsRef.current[name] = element;
} else {
delete inputsRef.current[name];
}
}, []);
const handleSubmit = () => {
const data = {};
Object.entries(inputsRef.current).forEach(([name, input]) => {
data[name] = input.value;
});
console.log('表单数据:', data);
};
const fields = ['name', 'email', 'phone', 'address'];
return (
<div>
{fields.map(field => (
<div key={field}>
<label>
{field.charAt(0).toUpperCase() + field.slice(1)}:
<input
ref={registerInput(field)}
defaultValue=""
/>
</label>
</div>
))}
<button onClick={handleSubmit}>提交</button>
</div>
);
}
三、受控组件适用场景
- 需要实时验证的表单
- 背景:当用户输入需要即时验证(如密码强度、邮箱格式)时,受控组件能实时访问状态并更新UI反馈
function EmailForm() {
const [email, setEmail] = useState('');
const [isValid, setIsValid] = useState(true);
const handleChange = (e) => {
const value = e.target.value;
setEmail(value);
setIsValid(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value));
};
return (
<div>
<input
type="email"
value={email}
onChange={handleChange}
style={{ borderColor: isValid ? 'green' : 'red' }}
/>
{!isValid && <p style={{ color: 'red' }}>邮箱格式不正确</p>}
</div>
);
}
- 表单字段间存在依赖关系
- 背景:当表单项需要基于其他字段动态变化时,受控组件能轻松管理状态间依赖
function ShippingForm() {
const [country, setCountry] = useState('US');
const [cities, setCities] = useState(['New York', 'Los Angeles']);
const handleCountryChange = (e) => {
const selectedCountry = e.target.value;
setCountry(selectedCountry);
if (selectedCountry === 'CN') {
setCities(['Beijing', 'Shanghai', 'Guangzhou']);
} else if (selectedCountry === 'JP') {
setCities(['Tokyo', 'Osaka']);
} else {
setCities(['New York', 'Los Angeles']);
}
};
return (
<div>
<select value={country} onChange={handleCountryChange}>
<option value="US">美国</option>
<option value="CN">中国</option>
<option value="JP">日本</option>
</select>
<select>
{cities.map(city => (
<option key={city} value={city}>{city}</option>
))}
</select>
</div>
);
}
- 需要动态增减表单项
- 背景:当用户可添加/删除多个同类表单项时,受控组件能有效管理动态表单状态
function MultiEmailInput() {
const [emails, setEmails] = useState(['']);
const addEmail = () => {
setEmails([...emails, '']);
};
const removeEmail = (index) => {
const newEmails = [...emails];
newEmails.splice(index, 1);
setEmails(newEmails);
};
const handleEmailChange = (index, value) => {
const newEmails = [...emails];
newEmails[index] = value;
setEmails(newEmails);
};
return (
<div>
{emails.map((email, index) => (
<div key={index}>
<input
type="email"
value={email}
onChange={(e) => handleEmailChange(index, e.target.value)}
/>
{emails.length > 1 && (
<button type="button" onClick={() => removeEmail(index)}>
删除
</button>
)}
</div>
))}
<button type="button" onClick={addEmail}>
添加邮箱
</button>
</div>
);
}
四、非受控组件的适用场景
- 文件上传输入
- 背景:文件输入必须使用非受控方式,因为文件数据无法通过JavaScript直接设置
function FileUpload() {
const fileInputRef = useRef();
const handleSubmit = async () => {
const file = fileInputRef.current.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
console.log('上传成功', await response.json());
} catch (error) {
console.error('上传失败', error);
}
};
return (
<div>
<input type="file" ref={fileInputRef} />
<button onClick={handleSubmit}>上传文件</button>
</div>
);
}
- 大型表单性能优化
- 背景:表单包含大量输入字段时,非受控组件可避免每次输入都触发渲染
function SurveyForm() {
const formRef = useRef();
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
const formData = new FormData(formRef.current);
const data = Object.fromEntries(formData.entries());
data.interests = formData.getAll('interests');
await submitSurvey(data);
alert('提交成功!');
} catch (error) {
console.error('提交失败', error);
} finally {
setIsSubmitting(false);
}
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
{/* 50+ 输入字段... */}
<div>
<label>兴趣:</label>
<input type="checkbox" name="interests" value="sports" /> 体育
<input type="checkbox" name="interests" value="music" /> 音乐
<input type="checkbox" name="interests" value="tech" /> 科技
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '提交中...' : '提交问卷'}
</button>
</form>
);
}
- 集成第三方DOM库
- 背景:当需要集成非React库(如地图、图表、富文本编辑器)时,非受控组件提供DOM访问能力
function RichTextEditor() {
const editorRef = useRef();
useEffect(() => {
const editor = new ThirdPartyEditor(editorRef.current, {
toolbar: ['bold', 'italic', 'link'],
onChange: (content) => {
console.log('内容更新:', content);
}
});
return () => {
editor.destroy();
};
}, []);
return <div ref={editorRef} style={{ height: '300px', border: '1px solid #ccc' }}></div>;
}
- 简单表单且只需要最终值
function SimpleSearch() {
const inputRef = useRef();
const handleSearch = (e) => {
e.preventDefault();
const query = inputRef.current.value.trim();
if (query) {
performSearch(query);
}
};
return (
<form onSubmit={handleSearch}>
<input
type="text"
ref={inputRef}
defaultValue=""
placeholder="搜索..."
/>
<button type="submit">搜索</button>
</form>
);
}