01_组件Hooks与状态
React 组件渲染、Hooks 规则、本地状态、副作用、列表与表单。
React 组件、Hooks 与状态
学习目标:从组件渲染模型理解 Hooks、状态更新和副作用,超越只背面试题。
1. 组件是界面的函数
React 组件根据 props 和 state 描述界面。重新渲染会重新调用组件函数;它不等于每次都重建整个 DOM。组件应尽量在相同输入下得到相同输出,不要在渲染过程中发网络请求或修改外部状态。
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(previous => previous + 1)}>
已点击 {count} 次
</button>
);
}
函数式更新 setCount(previous => previous + 1) 适合新值依赖旧值的情况。不要直接改数组或对象再传回原引用;创建新值可让状态变化更清楚。
2. Hooks 的位置与职责
- Hooks 在组件或自定义 Hook 的顶层调用,不放进条件、循环和普通回调中。
useState管本地可变状态;可由现有 props/state 计算出的值通常不需要再存一份。useEffect用于与组件外部系统同步,例如订阅、定时器或需要明确取消的请求;不要把它当成所有计算的默认位置。useMemo、useCallback是性能工具,应在确认开销或引用稳定性需求后使用。
3. 列表与表单
列表元素需要稳定的 key,优先使用业务 ID;可增删重排的列表不宜用索引作 key。受控表单把输入值放在 state 中,适合即时校验;也可以用原生表单能力和表单库减少样板代码。无论哪种方式,服务端仍需校验。
4. 状态边界
本地弹窗开关留在近处;可分享的筛选条件放 URL;来自服务端的笔记列表需要缓存、失效和重新获取策略,详见 状态与数据获取。Context 适合向子树传递稳定依赖,不应把所有频繁变化的数据都塞进单一 Context。
自测
useEffect适合存放纯字符串拼接吗?通常不适合,直接在渲染时计算。- 可重排列表为什么需要稳定 ID 作 key?让 React 正确追踪每个元素的身份。
5. 状态更新与渲染快照
组件一次渲染中的变量不会因为调用 setState 就立即变成新值。事件处理函数看到的是创建该函数时的状态快照;连续更新若依赖旧值,应使用函数式写法:
function addTwo() {
setCount(previous => previous + 1);
setCount(previous => previous + 1);
}
直接写两次 setCount(count + 1) 可能只得到加一。数组和对象更新也要创建新值,例如 setNotes(previous => previous.map(...)),避免直接修改原数组后让状态身份不变。
6. 什么时候需要 Effect
渲染期间能根据 props/state 算出的值,直接计算即可。Effect 用来与外部系统同步,如浏览器订阅、定时器或手写的数据请求;设置之后要返回清理函数,确保参数变化或组件卸载时释放旧资源。把“点击按钮才应发生”的提交操作放进事件处理函数,不要先设置状态再让 Effect 猜测是否提交。
useEffect(() => {
const timer = window.setInterval(() => setSeconds(s => s + 1), 1000);
return () => window.clearInterval(timer);
}, []);
这里清理定时器避免重复订阅。若 Effect 读取变化中的 props/state,应按 Hook 规则声明依赖,或重构状态流;不要为了消除警告随意留下空依赖数组。
7. 组件边界练习
做一个笔记列表:父组件管理搜索词,列表用稳定 ID 作为 key,表单有显式标签,删除按钮必须关联对应 ID。先用本地数组实现,再把数据读取移到 API 层。比较哪些状态属于组件、哪些状态应进入 URL 或服务端缓存。