React 19 入门教程
useState 深入
本教程共 50 篇 · 第 19 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
ReactuseState函数式更新惰性初始化状态更新
19. useState 深入
本节目标:掌握 useState 的高级用法,包括函数式更新、惰性初始化,以及对象/数组 state 的正确更新模式。
函数式更新
新状态依赖旧状态时,传函数给 setState:
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(prev => prev + 1);
}
function incrementThree() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// count 从 0 变成 3
}
return <button onClick={incrementThree}>{count}</button>;
}
prev 参数是 React 保证的最新状态值,不是闭包捕获的旧值。
闭包陷阱
在异步回调里用
count可能是过期的。函数式更新永远拿到最新值。
惰性初始化
初始值需要计算时,传函数而不是直接计算:
// 不好:每次渲染都执行 expensiveCalc()
const [data, setData] = useState(expensiveCalc());
// 好:只在首次渲染执行
const [data, setData] = useState(() => expensiveCalc());
function TodoList() {
// 从 localStorage 读取,只在首次渲染执行
const [todos, setTodos] = useState(() => {
const saved = localStorage.getItem('todos');
return saved ? JSON.parse(saved) : [];
});
}
什么时候用惰性初始化
初始值涉及 JSON 解析、localStorage 读取、大数组生成等开销操作时,用函数形式。
对象 state 的更新
展开运算符创建新对象,不要直接修改:
function Form() {
const [user, setUser] = useState({ name: '', age: 0 });
function handleNameChange(e) {
setUser({
...user, // 展开旧属性
name: e.target.value // 覆盖要改的
});
}
function handleAgeChange(e) {
setUser({
...user,
age: Number(e.target.value)
});
}
}
嵌套对象的更新
嵌套对象要逐层展开:
function Form() {
const [form, setForm] = useState({
name: '',
address: {
city: '',
street: ''
}
});
function handleCityChange(e) {
setForm({
...form,
address: {
...form.address,
city: e.target.value
}
});
}
}
嵌套越深越麻烦,后面会讲 Immer 简化这个操作。
数组 state 的更新
function List() {
const [items, setItems] = useState([]);
// 添加:展开旧数组 + 新元素
function addItem(item) {
setItems([...items, item]);
}
// 删除:filter
function removeItem(id) {
setItems(items.filter(item => item.id !== id));
}
// 更新某一项:map
function updateItem(id, newProps) {
setItems(items.map(item =>
item.id === id ? { ...item, ...newProps } : item
));
}
// 插入到指定位置
function insertAt(index, item) {
setItems([
...items.slice(0, index),
item,
...items.slice(index)
]);
}
}
数组操作速查
- 添加:
[...arr, newItem]或[newItem, ...arr]- 删除:
arr.filter(...)- 更新:
arr.map(...)- 插入:
[...arr.slice(0, i), item, ...arr.slice(i)]
函数作为 state
state 可以存函数,但不推荐。存数据,在渲染时计算:
// 不好:存函数
const [fn, setFn] = useState(() => () => console.log('hi'));
// 好:存数据,渲染时计算
const [mode, setMode] = useState('edit');
const isEdit = mode === 'edit';
常见错误
function Counter() {
const [count, setCount] = useState(0);
function badIncrement() {
count = count + 1; // 错误:直接修改
setCount(count + count); // 错误:基于闭包旧值
}
function goodIncrement() {
setCount(prev => prev + 1); // 正确
}
}
本节要点
- 新状态依赖旧状态用函数式更新
- 初始值开销大用惰性初始化
() => calc() - 对象/数组更新用展开运算符创建新引用
- 不要直接修改 state