副作用的适用场景
副作用的适用场景 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
写代码前先分类:这段逻辑是在响应用户动作,还是在做渲染计算,还是在跟外部系统同步?读完你应该能把放错位置的 Effect 挪回正确位置,并用渲染次数的变化证明挪对了。
问题场景
计数器满 3 的倍数时弹一个「连点 N 次!」的成就提示。第一版把它写成了 Effect:
const [count, setCount] = useState(0);
const [toast, setToast] = useState("");
useEffect(() => { // 没有 deps:每次渲染之后都执行
if (count > 0 && count % 3 === 0) {
setToast(`连点 ${count} 次!`);
}
});
能用,但每次点击都至少跑两轮:点击 → 渲染 → Effect → setToast → 再渲染。提示明明是对「用户点了按钮」的回应,却绕道渲染流程转了一圈。
核心解释
React 里只有三类位置,各有各的活:
渲染期计算:没有「发生」只有「算出」。状态、props 进来,UI 描述出去,必须纯。列表筛选、统计汇总(R17)属于这里。
事件处理:响应「用户做了什么」。点击、输入、提交都是明确的动作,处理逻辑放进对应的事件处理器——动作发生一次,逻辑跑一次,时机和次数都由用户控制。
Effect:同步「React 之外的世界」。和外部系统保持一致才轮到它:订阅消息源、同步 document.title、管理定时器。它跑在渲染之后,不是干业务逻辑的地方。
把成就提示挪回事件处理:
function handleClick() {
const next = count + 1;
setCount(next);
if (next % 3 === 0) setToast(`连点 ${next} 次!`); // 响应用户动作,一次点击跑一次
}
收益马上可见:toast 的更新和 count 的更新合并在同一次渲染里,Effect 版多出来的那一整轮渲染消失了。更重要的是因果清楚——提示是点击造成的,而不是「渲染之后发现 count 是 3 的倍数」这个副产品。将来产品要加「提示只弹一次」,你改事件处理就够了;Effect 版还得防着各种路径绕进来。
判断口诀:这段逻辑如果是因为「用户做了某件事」才要跑,放事件处理;如果是因为「数据是那样」就该显示,放渲染期;只有「不管什么原因导致重新渲染,我都得让外部世界跟上来」,才放 Effect。
示例拆解
实验区并排两个计数器,各带渲染计数器:
- 左边是 Effect 版:每次点击后 Effect 执行数 +1,弹提示那次更是连涨两次——因为它没有依赖数组,渲染几次跑几次,setToast 还额外触发一轮渲染;
- 右边是事件版:没有 Effect,提示直接写在点击处理里,点击一次只渲染一次;
- 两边的 toast 文案完全一样,差别全在渲染轮次和 Effect 执行计数上;
- 验证方式:连点到 6,对比两侧「渲染次数」——右边始终等于点击数,左边明显更多。
动手任务
打开本课实验区,完成三个小改动:
- 给左边 Effect 版补上
[count]依赖,观察 Effect 执行数的变化,再解释为什么仍然比事件版多渲染。 - 把 toast 增加自动消失:事件版在点击处理里用 setTimeout 关闭它(暂时不清理也没关系,R25 再收拾)。
- 说出实验里两个计数器各有一处「渲染期计算」(渲染次数的展示),确认它俩为什么不该用 Effect 实现。
常见错误
- 用 Effect 监听状态变化去做「响应动作」的事:提示、跳转、发请求通知后端,这些因果源头是用户动作,放 Effect 会失去触发时机和次数的控制。
- 用 Effect 链式同步多个 state:
useEffect(() => setB(f(a)), [a])是渲染期计算的替身,直接渲染时算出 B 就行。 - 把「每次渲染都要跑」当成需求:那通常说明你想做的是渲染期计算或外部同步,而不是无依赖的 Effect。
- 在事件处理里干渲染期计算的活:把派生值在点击时 set 进 state,另一个入口改了数据就漏更新;先分类再落位。
检查与复盘
完成下方检查题确认理解。自查清单:
- 能用「因果源头」判断逻辑归属:用户动作 → 事件,数据形状 → 渲染,外部世界 → Effect
- 知道无依赖 Effect 每次渲染都跑,评估过自己的 Effect 是否配得上这个频率
- 改完位置后会用渲染次数、日志这类可观察信号验证收益
参考资料
- React 官方文档:你可能不需要 Effect —— 本课分类法的完整展开
- React 官方文档:使用 Effect 进行同步 —— Effect 的正牌用途与执行时机
读完这一课了?
标记完成后,工作台会安排接下来的学习。