React使用useEffect调用Axios接口出现无限请求或数据不渲染问题
问题根因
- 状态更新的异步闭包陷阱:React的
setState是异步批量执行的,调用setClients(response.data)后,当前作用域下的clients仍然是本次渲染的旧值(初始空数组),你直接拿这个旧值做map生成rows、打印,自然拿到的是空结果。偶现渲染成功是因为极个别情况下渲染时序变化,刚好拿到了上一次更新的状态值。 - 函数引用变化导致死循环:组件每次渲染都会重新创建
fetchClients函数,如果你把它加入useEffect依赖,每次渲染都会触发useEffect重新执行,调用接口更新状态后又触发重渲染,形成无限调用的死循环。 - loading状态逻辑错误:你将
setLoading(false)写在了请求判断逻辑之外,且请求完成后设置loading为true,和常规加载逻辑完全相反,会导致请求触发逻辑异常。
修复方案
方案1:直接将请求逻辑写在useEffect内(仅挂载时请求一次场景推荐)
不需要额外处理函数依赖,代码更简洁:
const [clients, setClients] = useState([]); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { // 加组件挂载标识,避免组件卸载后更新状态触发警告 let isMounted = true; if (loading) return; setLoading(true); axios .get(API_URL_CLIENTS + "?users_permissions_user.email=" + USER_KEY_EMAIL) .then((response) => { if (!isMounted || response.status !== 200) return; const resData = response.data; setClients(resData); // 直接用接口返回数据生成rows,避开闭包陷阱 const newRows = resData.map((client) => ({ id: client.id, col1: client.nom, col2: client.prenom, col3: client.mail, col4: client.telephone, })); setRows(newRows); }) .catch((error) => { console.log(error); }) .finally(() => { if (isMounted) setLoading(false); }); return () => { isMounted = false; }; }, []); // 依赖为空,仅挂载时执行一次
方案2:用useCallback缓存fetchClients(需要多处调用请求场景推荐)
如果需要在组件其他地方也调用fetchClients,可以用useCallback缓存函数引用避免死循环:
import { useState, useEffect, useCallback } from 'react'; const [clients, setClients] = useState([]); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const fetchClients = useCallback(() => { if (loading) return; setLoading(true); axios .get(API_URL_CLIENTS + "?users_permissions_user.email=" + USER_KEY_EMAIL) .then((response) => { if (response.status !== 200) return; const resData = response.data; setClients(resData); const newRows = resData.map((client) => ({ id: client.id, col1: client.nom, col2: client.prenom, col3: client.mail, col4: client.telephone, })); setRows(newRows); }) .catch((error) => { console.log(error); }) .finally(() => { setLoading(false); }); }, [loading]); useEffect(() => { fetchClients(); }, [fetchClients]); // 加入缓存后的函数依赖,不会触发死循环
可选优化
如果没有单独修改rows的需求,你可以用useMemo直接从clients派生rows,不需要单独维护rows状态,减少冗余状态避免不同步问题:
import { useMemo } from 'react'; const rows = useMemo(() => { return clients.map((client) => ({ id: client.id, col1: client.nom, col2: client.prenom, col3: client.mail, col4: client.telephone, })); }, [clients]);
内容的提问来源于stack exchange,提问作者Youssef Hammouma
相关产品推荐
相关产品推荐

