React useEffect请求reject时无限循环、成功时仅执行一次的修复
问题原因
useEffect对依赖项的变更做 浅比较 校验:原始类型值变了、或者引用类型的内存地址变了,就会重新执行回调。你写的effect依赖数组是[success, error],两个状态任意一个变动都会触发回调重跑。- 请求成功时不会死循环:组件首次挂载执行effect调用接口,返回200后执行
setSuccess(true),success从false变true触发一次effect重跑;重跑后再次调用接口依然返回成功,此时success已经是true,重复设置相同布尔值不会触发状态更新,且全程不会进入catch修改error,error始终是初始化时的对象引用,依赖不再变化,循环终止,所以成功提示只弹一次。 - 请求报错时死循环的触发链路:首次执行effect调用接口失败 → 进入catch执行
setError({ status: true, message: error.message })→ 每次setError传入的都是 新建的对象字面量,哪怕内容和之前完全一样,内存地址和旧状态不同,React判定error依赖变动 → 触发effect重跑 → 再次调用getPosts()发起请求 → 请求再次失败 → 再次setError传入新对象 → 再次触发effect重跑,无限往复,导致错误提示不停弹出。 - 根因是逻辑耦合:把「组件挂载时拉取一次数据」的一次性逻辑,和「监听状态弹提示」的逻辑混在了同一个effect里,再加上对象状态更新时每次生成新引用,共同触发了死循环。
修复方案
- 拆分耦合逻辑:接口拉取逻辑单独放在挂载阶段执行的effect中,依赖数组传
[],保证整个生命周期只调用一次,不会被状态变更触发重复请求。 - toast提示直接放在请求的then/catch回调里触发,不需要额外监听状态,从根源避免重复执行。
- 修正后的完整代码如下:
import React, { useState, useEffect } from 'react'; import Loader from './../components/Loader'; import axios from 'axios'; import { toast } from 'react-toastify'; const PostsTD = () => { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState({ status: false, message: '', }); const [success, setSuccess] = useState(false); const getPosts = () => { axios .get('http://localhost:5050/posts') .then((res) => { setPosts(res.data); setLoading(false); if (res.status === 200) { setSuccess(true); toast.success('Success!'); } }) .catch((error) => { setLoading(false); setError({ status: true, message: error.message }); toast.error(error.message); }); }; // 仅组件挂载时执行一次数据拉取 useEffect(() => { getPosts(); }, []); if (loading) { return ( <div className="px-28 text-center py-12"> <Loader /> </div> ); } return ( <div className="md:px-28 px-6"> <h1 className="text-center font-extrabold text-gray-400 my-4 text-4xl">POSTS FETCHED USING AXIOS</h1> {posts?.length > 0 ? ( <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 py-2 px-3 bg-red-500"> {posts.map((post, idx) => { return ( <div key={idx} className="p-3 bg-red-200 text-gray-900 rounded-md"> <h2 className="font-semibold text-xl">{post.title}</h2> <p className="font-normal my-3">{post.body}</p> </div> ); })} </div> ) : ( <h1>NO posts to render</h1> )} </div> ); }; export default PostsTD;
- 可选优化:如果业务场景确实需要监听状态触发副作用,单独拆分一个effect处理提示逻辑,不要在这个effect中包含请求调用;更新对象/数组类型的状态时,先判断内容是否真的发生变化再调用setState,避免无意义的新引用触发重渲染或effect执行。
内容的提问来源于stack exchange,提问作者vipul_dev_web
相关产品推荐
相关产品推荐

