React useFetch请求API偶发Cannot read properties of undefined报错
问题根因
你的判断方向没错,但现有代码有两个明确问题导致偶发报错、数据拉取不稳定:
- 渲染逻辑存在边界漏洞:仅判断了
data是否存在,既没处理接口请求失败、返回数组长度不足的场景,还错误地拿固定长度为5的images数组做map遍历基准。一旦接口返回慢、请求出错导致data没被正确赋值、或者接口临时返回的数组项少于5个,遍历到超出data长度的索引时,data[i]就会是undefined,读取type/count属性时直接触发"Cannot read properties of undefined"报错。 - 自定义Hook存在请求竞态问题:没有处理组件卸载、url快速变更触发重复请求的场景,后发先至的旧请求会覆盖正确的返回数据,直接导致数据偶发异常。
修复代码
1. 修复useFetch Hook
// useFetch.js import { useEffect, useState } from "react"; import axios from "axios"; const useFetch = (url) => { // data初始值设为空数组,避免初始状态为undefined const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { // 新增请求取消标记,解决竞态问题 const abortController = new AbortController(); const fetchData = async () => { setLoading(true); setError(null); try { const res = await axios.get(url, { signal: abortController.signal }); setData(res.data); } catch (err) { // 主动取消的请求不需要更新错误状态 if (!abortController.signal.aborted) { setError(err); } } finally { // 组件已卸载的话不更新loading状态,避免内存泄漏 if (!abortController.signal.aborted) { setLoading(false); } } }; fetchData(); // 组件卸载、url变更时取消上一次未完成的请求 return () => abortController.abort(); }, [url]); return { data, loading, error }; }; export default useFetch;
2. 修复PropertyList组件渲染逻辑
// PropertyList.jsx import React from "react"; import useFetch from "../../hooks/useFetch"; import "./propertyList.css"; function PropertyList() { const { data, loading, error } = useFetch( "http://localhost:3000/api/hotels/countByType" ); const images = [ "https://media-cdn.tripadvisor.com/media/photo-s/1c/d3/b2/4f/exterior-view.jpg", "https://www.phillyaptrentals.com/wp-content/uploads/2020/12/apartment-building-what-makes-good-apartment-building-scaled.jpg", "https://dynamic-media-cdn.tripadvisor.com/media/photo-o/1a/01/04/c9/manduna-resort.jpg?w=900&h=-1&s=1", "https://images.squarespace-cdn.com/content/v1/585562bcbe659442d503893f/c3b765c0-45e3-46b3-9ff9-b4101fb30674/01.+Exotik+Villas+Bali+-+Aloui.jpg", "https://vancouverisland.travel/app/uploads/2021/04/wood-cabin-on-vancouver-island_BenGiesbrecht.jpg", ]; // 错误场景兜底 if (error) return <div className="pList">数据加载失败,请刷新重试</div>; return ( <div className="pList"> {loading ? ( "Loading.." ) : ( <> {/* 以接口返回的data为遍历基准,从根源避免索引越界 */} {data.map((item, i) => { // 数据项缺失、无对应配图时直接跳过渲染 if (!item || !images[i]) return null; return ( <div className="pListItem" key={item.type}> <img src={images[i]} alt={item.type} className="pListImg" /> <div className="pListTitles"> <h1>{item.type}</h1> <h2> {item.count} {item.type} </h2> </div> </div> ); })} </> )} </div> ); } export default PropertyList;
关键修复点说明
- 给
data设置初始空数组,避免初始undefined导致的判断失效 - 加入
AbortController处理请求竞态和组件卸载后的状态更新,解决内存泄漏、旧请求覆盖新数据的问题 - 调整遍历逻辑,不再用固定长度的配图数组做遍历基准,完全以接口返回的数据长度为准,彻底解决索引越界读不到属性的问题
- 增加错误场景兜底和单条数据合法性判断,哪怕接口返回结构异常也不会直接抛错白屏
- 列表key从索引替换为接口返回的唯一字段
type,避免React渲染列表出现状态错乱
内容的提问来源于stack exchange,提问作者Varun Nayak
相关产品推荐
相关产品推荐

