74 lines
2.8 KiB
Markdown
74 lines
2.8 KiB
Markdown
---
|
|
title: Use Functional setState Updates
|
|
impact: MEDIUM
|
|
impactDescription: 防止闭包引用过期以及不必要的回调重建
|
|
tags: react, hooks, useState, useCallback, callbacks, closures
|
|
|
|
## 使用函数式 setState 更新
|
|
|
|
当基于当前状态值来更新状态时,应使用 setState 的函数式更新形式,而不是直接引用状态变量。这可以防止闭包引用过期、消除不必要的依赖,并创建稳定的回调引用。
|
|
|
|
**错误做法(需要将 items 作为依赖):**
|
|
|
|
```tsx
|
|
function TodoList() {
|
|
const [items, setItems] = useState(initialItems)
|
|
|
|
// 回调必须依赖 items,每次 items 变化时都会重建
|
|
const addItems = useCallback((newItems: Item[]) => {
|
|
setItems([...items, ...newItems])
|
|
}, [items]) // ❌ items 依赖导致回调频繁重建
|
|
|
|
// 如果忘记添加依赖,则会存在闭包引用过期的风险
|
|
const removeItem = useCallback((id: string) => {
|
|
setItems(items.filter(item => item.id !== id))
|
|
}, []) // ❌ 缺少 items 依赖——将使用过期的 items!
|
|
|
|
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
|
|
}
|
|
```
|
|
|
|
第一个回调在每次 `items` 变化时都会重建,这可能导致子组件不必要地重新渲染。第二个回调存在闭包引用过期的 bug——它将始终引用初始的 `items` 值。
|
|
|
|
**正确做法(稳定的回调,无闭包过期):**
|
|
|
|
```tsx
|
|
function TodoList() {
|
|
const [items, setItems] = useState(initialItems)
|
|
|
|
// 稳定的回调,永远不会重建
|
|
const addItems = useCallback((newItems: Item[]) => {
|
|
setItems(curr => [...curr, ...newItems])
|
|
}, []) // ✅ 不需要依赖
|
|
|
|
// 始终使用最新状态,无闭包过期风险
|
|
const removeItem = useCallback((id: string) => {
|
|
setItems(curr => curr.filter(item => item.id !== id))
|
|
}, []) // ✅ 安全且稳定
|
|
|
|
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
|
|
}
|
|
```
|
|
|
|
**优点:**
|
|
|
|
1. **稳定的回调引用**——状态变化时无需重建回调
|
|
2. **无闭包过期**——始终操作最新的状态值
|
|
3. **更少的依赖**——简化依赖数组,减少内存泄漏
|
|
4. **防止 bug**——消除了 React 闭包 bug 最常见的来源
|
|
|
|
**何时使用函数式更新:**
|
|
|
|
- 任何依赖于当前状态值的 setState
|
|
- 在 useCallback/useMemo 中需要用到状态时
|
|
- 引用状态的事件处理函数
|
|
- 更新状态的异步操作
|
|
|
|
**何时直接更新即可:**
|
|
|
|
- 将状态设置为静态值:`setCount(0)`
|
|
- 仅通过 props/参数设置状态:`setName(newName)`
|
|
- 状态不依赖于前一个值
|
|
|
|
**注意:** 如果你的项目已启用 [React Compiler](https://react.dev/learn/react-compiler),编译器可以自动优化某些情况,但为了正确性和防止闭包过期 bug,仍然建议使用函数式更新。
|