React中useEffect钩子引发无限循环的原因解惑
核心问题拆解
你的代码陷入无限重渲染循环,根源在于useEffect依赖数组的判断逻辑和JavaScript对象的引用特性:
对象引用的不可变性
初始状态starWarsData是一个空对象{},当fetch请求完成后,setStarWarsData(data)会将状态替换为API返回的全新对象。哪怕两次对象的键值对完全一致,在JavaScript中它们属于不同的引用地址——React对比依赖时,只会判断引用是否相同,而非内容是否一致。useEffect的触发逻辑
你把starWarsData放进了useEffect的依赖数组。每次组件重渲染后,React会对比依赖数组的前后值:由于新的starWarsData是新引用对象,对比结果为“不相等”,因此useEffect会再次执行。无限循环的形成
useEffect再次执行→发起新的fetch请求→调用setStarWarsData更新状态→状态变化触发组件重渲染→依赖数组对比不相等→useEffect再次执行……循环往复,最终导致无限重渲染。
你的理解误区
你误以为“内容一致就不会触发useEffect”,但React对引用类型(对象、数组)的依赖判断是基于引用地址,而非内容深度对比。只要引用地址不同,就会被判定为依赖变化。
解决方案
这个场景下,fetch只需要在组件首次挂载时执行一次,直接将依赖数组改为空数组即可:
import React from "react" export default function App() { const [starWarsData, setStarWarsData] = React.useState({}) console.log(starWarsData) React.useEffect(function() { fetch("https://swapi.dev/api/people/1") .then(res => res.json()) .then(data => setStarWarsData(data)) }, []) // 空数组表示仅在组件挂载时执行一次 return ( <div> <pre>{JSON.stringify(starWarsData, null, 2)}</pre> </div> ) }
如果后续需要根据对象内容变化重新触发请求,可以使用自定义的深度对比Hook(如useDeepCompareEffect),或者将对象的关键属性单独放入依赖数组,但当前场景下空数组是最直接的正确方案。
内容的提问来源于stack exchange,提问作者Grease

