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. 快速点击两次前进按钮的完整时间线
- 第一次点击前进:
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。
- 执行
- 第二次点击前进:
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。
- 执行
- 异步请求完成阶段
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
相关产品推荐
相关产品推荐

