函数组件与 useState:从「类组件 this」到「每次渲染一次快照」
知识背景
React 16.8 起,Hooks 让函数组件具备状态与生命周期能力,逐渐成为默认写法。useState 是最基础的 Hook:在函数组件里声明一块可随交互变化的 UI 状态。
若你习惯了类组件的 this.state / this.setState,需要切换心智:函数组件没有实例,每次渲染都是一次函数执行;useState 由 React 在内部按调用顺序记住「第几个 state 对应哪个槽位」。
知识详解与通俗解释
1. useState 基本形态
jsx
const [count, setCount] = useState(0);- 初值:只在首次挂载时用作初始 state;传入函数
useState(() => expensive())时,该函数也仅在首次挂载调用(惰性初始化)。 - 更新:
setCount(1)或setCount((c) => c + 1)。函数式更新在「新值依赖旧值」时更安全,避免闭包读到过期的count。
通俗说:count 是当前这次渲染的「快照」;用户点了按钮,React 会安排重渲染,下次执行函数时 count 才是新值。
2. 批量更新与异步观感
在 React 18 中,多数场景下同一事件里的多次 setState 会被批处理,只触发一次重渲染。不要在 setCount 的下一行立刻假设 DOM 已更新——需要读最新 DOM 时用 useEffect / useLayoutEffect 或回调 ref。
3. 常见踩坑:把 state 当「实时全局变量」
每次渲染里的 count 都固定;若在 setTimeout、Promise 回调里闭包捕获了旧的 count,会看到「点了三次还是1」的现象。修法:函数式更新 setCount(c => c + 1),或减少对外层 count 的依赖。
4. 与类组件 setState 的差异(面试常提)
- 类组件
setState可部分合并对象;useState不会自动合并对象——更新对象要自己展开:setUser(u => ({ ...u, name: 'x' }))。 - Hooks 不能在条件/循环里随意调用(会破坏顺序),必须放在组件顶层。
总结
useState让函数组件拥有本地状态;初值、惰性初始化、函数式更新是三个高频考点。- 理解「一次渲染一份快照」后,闭包与过时 state 问题会好排很多。
- 对象/数组更新要记得不可变写法(展开或拷贝后再改),便于 React 比对引用变化。