Kodokon kodokon.com

useEffect:副作用、依赖与清理

使用 useEffect 让你的组件与外部世界同步,掌握依赖项与清理。

9 分钟 · 3 题

在 Kodokon 中打开本课

一个 React 组件根据 props 和 state 计算出 JSX。任何超出这个范畴的事情——修改文档标题、启动定时器、订阅浏览器事件——都是副作用useEffect 会在渲染之后执行这段代码,而不阻塞显示,并在某些值发生变化时重新同步它。

JSX
import { useEffect } from "react";

function CartTitle({ count }) {
  useEffect(() => {
    document.title = `Cart (${count})`;
  }, [count]);

  return <p>Items: {count}</p>;
}
只有当 count 变化时,该副作用才会重新执行。

依赖数组控制着副作用的执行频率。[count]:在每一次 count 发生变化的渲染之后,副作用都会重新执行。[]:副作用只在首次渲染之后执行一次。不传数组:副作用会在每一次渲染之后重新执行,而这很少是你想要的。黄金法则:副作用内部用到的每一个组件值都必须出现在数组中。

JSX
import { useEffect, useState } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setSeconds((s) => s + 1);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <p>Elapsed: {seconds} s</p>;
}
如果没有 clearInterval,定时器就会比组件活得更久。

副作用返回的那个函数就是清理函数。React 会在每次重新执行副作用之前调用它,然后在组件卸载时最后再调用一次。正是它防止了泄漏:被遗弃的定时器、堆叠起来的监听器、永远没被取消的订阅。每一个“打开”了某样东西的副作用都必须把它再关上。

JSX
useEffect(() => {
  function onResize() {
    setWidth(window.innerWidth);
  }
  window.addEventListener("resize", onResize);

  return () => {
    window.removeEventListener("resize", onResize);
  };
}, []);
订阅 resize 事件,并在卸载时取消订阅。

知识检测

确认你已牢记本课的重点内容。

  1. 使用 useEffect(fn, []) 时,函数 fn 在什么时候执行?
    • 在组件的每一次渲染之前
    • 仅在首次渲染之后
    • 在每一次状态更新时
  2. 副作用返回的清理函数在什么时候被调用?
    • 仅当组件被卸载时
    • 在副作用每次重新执行之前,以及在卸载时
    • 就在组件首次渲染之前
    • 永远不会自动调用:你必须自己调用它
  3. 一个副作用读取了 userId,但它的依赖数组是 []。问题出在哪里?
    • 当 userId 变化时副作用不会重新执行,并会保留一个过时的值
    • React 会在首次渲染时抛出错误
    • 副作用会陷入无限循环重复执行