React useEffect发起axios请求首次返回空数组致取值报错
问题原因
useEffect的执行时机是组件首次挂载渲染完成后,内部的axios异步请求存在网络耗时,因此Hook首次返回的products永远是useState初始化的空数组[],等接口响应完成后才会更新为真实数据- 渲染时未做任何边界判断:如果接口返回数据结构不符合预期、数组遍历过程中取到了不存在的对象属性,就会拿到
undefined值,直接传入组件渲染时触发报错,报错示例如下:
- 原Hook还存在两个隐性问题:
useEffect依赖数组为空,传入的API地址变更时不会重新发起请求;未做组件卸载时的请求状态处理,容易触发内存泄漏警告。
修复方案
第一步:优化自定义Hook逻辑
补充加载状态、错误兜底、数据格式校验、依赖修正以及卸载保护,代码如下:
const useGetProducts = (API) => { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let isUnmounted = false; setLoading(true); async function fetchData() { try { const response = await axios(API); if (!isUnmounted) { // 强制兜底为数组格式,避免接口返回非数组结构导致后续遍历报错 setProducts(Array.isArray(response.data) ? response.data : []); setError(null); } } catch (err) { if (!isUnmounted) { setError(err); setProducts([]); } } finally { if (!isUnmounted) { setLoading(false); } } } fetchData(); // 组件卸载时标记,避免更新已卸载组件的状态 return () => { isUnmounted = true; }; }, [API]); // 补全API依赖,地址变更时自动重新拉取数据 return { products, loading, error }; };
第二步:渲染层增加边界判断
不要拿到Hook返回值直接渲染,根据加载、错误、空数据状态分别做兜底处理:
// 业务组件中使用Hook const { products, loading, error } = useGetProducts("/api/products"); // 加载中兜底 if (loading) return <div>商品列表加载中...</div>; // 请求错误兜底 if (error) return <div>加载失败,请刷新页面重试</div>; return ( <div className="product-container"> {/* 校验数组有效后再遍历 */} {products.length > 0 && products.map((product) => { // 过滤数组中的无效空项 if (!product) return null; return <ProductItem key={product.id} product={product} />; })} {/* 空列表兜底 */} {products.length === 0 && <div>暂无相关商品</div>} </div> );
可选优化项
- 如果
ProductItem组件内部存在深层属性取值(比如product.shop.logo这类链式取值),统一使用可选链操作符product?.shop?.logo做兜底,避免中间属性不存在触发报错 - 列表渲染必须传入唯一
key值,禁止直接使用数组下标作为key,防止列表动态更新时出现渲染错位问题 - 如果接口请求耗时较长,可以在Hook中加入AbortController主动中断未完成的请求,进一步优化性能
内容的提问来源于stack exchange,提问作者alejandro
相关产品推荐
相关产品推荐

