You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React useEffect清理函数执行顺序解惑:过期副作用处理

React useEffect清理函数执行顺序困惑解答

问题描述

正在学习React的useEffect清理函数,对以下示例的执行顺序存在困惑,核心疑问:

  • useEffect中清理函数清理过期副作用的执行顺序是怎样的?
  • 连续快速点击两次前进按钮后,最后三条日志Effect ran | id: [3]→ignore is: [true] | id: [2]→Pokemon was set in state | id: [3]的流程是如何发生的?

示例代码

import * as React from "react"
import { fetchPokemon } from "./api"
import Carousel from "./Carousel"
import PokemonCard from "./PokemonCard"

export default function App () {
  const [id, setId] = React.useState(1)
  const [pokemon, setPokemon] = React.useState(null)
  const [loading, setLoading] = React.useState(true)
  const [error, setError] = React.useState(null)

  const handlePrevious = () => {
    if (id > 1) {
      setId(id - 1)
    }
  }

  const handleNext = () => setId(id + 1)

  React.useEffect(() => {
    let ignore = false

    console.log(`Effect ran | id: [${id}]`)

    const handleFetchPokemon = async () => {
      setLoading(true)
      setError(null)

      const { error, response } = await fetchPokemon(id)

      if (ignore) {
        console.log(`ignore is: [${ignore}] | id: [${id}]`)
        return
      } else if (error) {
        setError(error.message)
      } else {
        console.log(`Pokemon was set in state | id: [${id}]`)
        setPokemon(response)
      }

      setLoading(false)
    }

    handleFetchPokemon()

    return () => {
      console.log(`cleanup function ran | id: [${id}]`)
      ignore = true
    }
  }, [id])

  return (
    <Carousel onPrevious={handlePrevious} onNext={handleNext}>
      <PokemonCard
        loading={loading}
        error={error}
        data={pokemon}
      />
    </Carousel>
  )
}

日志信息

初始渲染时

Effect ran | id: [1]
Pokemon was set in state | id: [1]

连续快速点击两次前进按钮后

cleanup function ran | id: [1]
Effect ran | id: [2]
cleanup function ran | id: [2]
Effect ran | id: [3]
ignore is: [true] | id: [2]
Pokemon was set in state | id: [3]

执行流程解析

1. 清理函数的核心逻辑

每个useEffect实例都绑定了当前渲染周期的id值和独立的ignore闭包变量:

  • 当组件因id更新触发重新渲染时,先执行上一次渲染对应的useEffect清理函数,再执行当前渲染的useEffect。
  • 清理函数的作用是标记当前实例的异步请求为失效,阻止其完成后更新状态。

2. 快速点击两次前进按钮的完整时间线

  1. 第一次点击前进:id从1变为2,组件重新渲染
    • 执行id=1的useEffect清理函数:打印cleanup function ran | id: [1],将该实例的ignore设为true(此时id=1的请求已完成,无影响)。
    • 执行id=2的useEffect:打印Effect ran | id: [2],发起id=2的异步请求,此时该实例ignore为false。
  2. 第二次点击前进:id从2变为3,组件再次重新渲染
    • 执行id=2的useEffect清理函数:打印cleanup function ran | id: [2],将该实例的ignore设为true(此时id=2的请求仍在pending状态)。
    • 执行id=3的useEffect:打印Effect ran | id: [3],发起id=3的异步请求,该实例ignore为false。
  3. 异步请求完成阶段
    • id=2的请求先完成:回到对应handleFetchPokemon函数,检查ignore已为true,打印ignore is: [true] | id: [2]后直接return,不更新状态。
    • id=3的请求后完成:回到对应handleFetchPokemon函数,ignore仍为false,打印Pokemon was set in state | id: [3]并调用setPokemon更新状态。

关键细节

  • 每个useEffect的ignore变量是独立闭包变量,仅对当前实例的异步逻辑生效。
  • React严格遵循「清理上一次副作用→执行当前副作用」的顺序,确保过期请求不会干扰最新状态。

内容的提问来源于stack exchange,提问作者Tomas Rodriguez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 18:22:12