前言
useState 是我们在 React 函数式组件中处理状态的基石。看似简单的 API 背后,其实隐藏着 React 的渲染调度机制。本文将带你透彻理解状态更新的异步性与批量处理逻辑。
一、 基础语法与类型定义
在 TSX 中,useState 支持多种类型定义,这能确保我们的状态更新符合预期。
import React, { useState } from 'react';
const Counter: React.FC = () => {
// 1. 使用number型定义状态类型
const [count, setCount] = useState<number>(0);
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
};
export default Counter;
二、 核心特点深度剖析
1. 异步更新与函数式更新
setState 本身并不是同步执行的。如果你需要基于当前状态计算下一个状态,务必使用回调函数形式setState((pre)⇒{})。
// ❌ 错误做法:在同一个事件中多次调用
const handleAdd = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// 结果只增加了 1,因为三次 count 拿到的都是同一个旧值
};
// ✅ 正确做法:使用回调函数(推荐)
const handleAddFixed = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 结果增加了 3
};
2. 状态相等跳过渲染
React 使用 Object.is 比较算法。如果更新的状态值与当前值完全相等,React 将会跳过子组件的渲染和 Effect 的执行,从而优化性能。
3. 自动批处理(Automatic Batching)
React 会将多个状态更新合并成一次渲染,这被称为“批量处理”。
三、 React 18 的重大变革
1. 异步场景下的批处理
- React 18 之前:在
setTimeout、Promise或原生 DOM 事件中,setState是同步的,每次调用都会立即更新状态并触发渲染。 - React 18 之后:引入了全自动批处理。即使在异步操作中,多个状态更新也会合并为一次渲染。
2. 强制同步:ReactDOM.flushSync
如果你在某些极端场景下(例如:更新后需要立即读取 DOM 属性)需要强制同步更新,可以使用 flushSync。
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
const SyncDemo: React.FC = () => {
const [count, setCount] = useState<number>(0);
const handleClick = () => {
// 强制 React 立即同步更新 DOM
flushSync(() => {
setCount(prev => prev + 1);
});
// 执行到这里时,DOM 已经更新完成
console.log('DOM 已更新');
};
return <button onClick={handleClick}>同步更新 {count}</button>;
};
export default SyncDemo;
四、 使用注意事项(避坑指南)
-
不可变性 (Immutability) :不要直接修改状态对象。
- ❌
state.name = 'new' - ✅
setState({ ...state, name: 'new' })
- ❌
-
顶层调用:不要在循环、条件判断或嵌套函数中调用 Hook,确保 Hook 的调用顺序在每次渲染中保持一致。
-
初始参数:如果初始状态需要复杂计算,可以传入一个函数
useState(() => computeExpensively()),该函数只会在初始渲染时执行一次。