React Hooks中useCallback与useEffect引发无限循环问题咨询
React useCallback 配合 useEffect 触发无限循环的原因分析
问题复现代码
import axios from "axios"; import { useEffect, useState, useCallback } from "react"; export default function App() { const [user, setUser] = useState([]); const getUser = useCallback(async () => { let { data } = await axios.get( "https://jsonplaceholder.typicode.com/users" ); setUser(data); }, [user]); useEffect(() => { getUser(); }, [getUser]); return ( <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen!</h2> </div> ); }
核心差异原因
两种依赖项表现不一致,本质是依赖本身的引用稳定性完全不同,顺着组件渲染执行链路可以直接拆解出区别:
依赖项写user触发无限循环的执行逻辑
- 组件首次挂载时,
user初始值为空数组[],useCallback基于初始依赖生成第一个getUser函数引用,useEffect检测到getUser为新引用,执行回调发起接口请求 - 接口返回后执行
setUser(data),新返回的用户数据和初始空数组值、引用均不相同,React触发组件重渲染 - 重渲染阶段,
useCallback检测到依赖项user和上一轮渲染的值不一致,会生成全新的getUser函数引用 useEffect对比依赖发现getUser引用变化,再次执行回调发起请求,请求返回后又触发setUser更新,重渲染再次生成新的getUser引用,整个链路无限重复
哪怕接口两次返回的数据内容完全一致,只要是新生成的数组、对象类型值,引用地址就和旧值不同,依然会触发依赖更新。
依赖项写setUser正常运行的逻辑
React内部对useState返回的状态更新函数做了特殊处理,setUser的引用在组件整个生命周期内是固定不变的,不会随着组件重渲染生成新引用:
- 首次挂载基于依赖
setUser生成getUser函数后,后续每一轮重渲染时,useCallback检测到依赖项没有变化,会一直返回同一个getUser函数引用 useEffect对比依赖发现getUser引用没有变化,就不会重复执行effect回调,自然不会触发无限循环。
符合Hook依赖规则的正确写法
示例代码中getUser函数内部根本没有读取user状态的值,本身就不需要把user加入useCallback依赖数组,仅依赖引用稳定的setUser完全符合Hook规则,不是绕开校验的取巧写法:
const getUser = useCallback(async () => { let { data } = await axios.get( "https://jsonplaceholder.typicode.com/users" ); setUser(data); }, [setUser]);
如果函数逻辑中确实需要用到上一次的user状态值,可以用setUser的函数式更新写法,不需要在外层依赖user,同样可以避免循环:
const getUser = useCallback(async () => { let { data } = await axios.get("接口地址"); // 函数式更新可以直接拿到上一次的state值,无需引入外部依赖 setUser(prevUser => { // 基于prevUser和新的data处理后返回新状态即可 return [...prevUser, ...data] }) }, [setUser])
内容的提问来源于stack exchange,提问作者Lobsang Gyatso
相关产品推荐
相关产品推荐

