React自定义CRUD useApi Hook调用list方法触发无限循环问题求助
问题根因
- 第一个错误:在
useApi的顶层渲染逻辑中直接调用setApi更新状态。setApi触发状态更新后会导致组件重渲染,重渲染时又会生成新的list、create函数实例,再次触发setApi更新,形成死循环的第一个触发源。 - 第二个错误:业务组件的
useEffect把api对象作为依赖项。由于上面的逻辑api对象每次渲染都会生成新的引用,useEffect会反复执行调用api.list(),list内部更新apiData、isLoading状态又触发重渲染,进一步加剧死循环。 - 额外问题:
create方法中fetch的传参写法错误,fetch仅接收两个参数:第一个是url,第二个是完整的配置对象,请求体需要放在配置对象的body字段中,你当前写的三个参数、直接重写headers的写法完全不生效。
修复方案
第一步:重构useApi Hook
把api方法集合用useCallback包裹方法,再用useMemo生成固定引用的对象,避免不必要的引用更新,同时修正fetch传参错误:
import { useState, useCallback, useMemo } from "react"; const useApi = (baseUrl, defaultHeaders = {}) => { const [isLoading, setIsLoading] = useState(false); const [apiData, setApiData] = useState(null); const [serverError, setServerError] = useState(null); const list = useCallback(async () => { setIsLoading(true); setServerError(null); try { const resp = await fetch(baseUrl, { method: "GET", headers: defaultHeaders }); const data = await resp.json(); setApiData(data); } catch (error) { setServerError(error); } finally { setIsLoading(false); } }, [baseUrl, defaultHeaders]); const create = useCallback(async (payload) => { setIsLoading(true); setServerError(null); try { const resp = await fetch(baseUrl, { method: "POST", headers: { "Content-Type": "application/json", ...defaultHeaders }, body: JSON.stringify(payload) }); const data = await resp.json(); setApiData(data); return data; } catch (error) { setServerError(error); } finally { setIsLoading(false); } }, [baseUrl, defaultHeaders]); // 用useMemo生成固定引用的api对象,只有依赖变化才更新 const api = useMemo(() => ({ list, create }), [list, create]); return { isLoading, apiData, serverError, api }; }; export default useApi;
第二步:修改业务组件的useEffect依赖
只在组件挂载时调用一次list即可:
import { useEffect } from "react"; import useApi from "./useApi"; export default function App() { const { isLoading: loading, apiData: students, serverError: error, api } = useApi("https://59f0f160ce72350012bec011.mockapi.io/students"); useEffect(() => { api.list(); // 空依赖数组代表仅挂载时执行一次 }, []); if (error) return <div>请求出错:{error.message}</div> return ( <div className="App"> <h1>list</h1> {loading ? "loading" : students?.map((x) => x.name)} </div> ); }
内容的提问来源于stack exchange,提问作者rsilva
相关产品推荐
相关产品推荐

