React动态路由用户详情页刷新时API数据未加载致页面崩溃
我正在开发React项目,已实现从外部API拉取用户信息并在UI端通过无限滚动加载展示列表的功能,经查阅资料完成了无限滚动逻辑的编写。
目前我正在实现动态路由功能:用户点击承载用户信息的卡片后,将跳转至该用户的个人详情页,通过另一个API接口拉取并展示该用户的更多个人信息,目前路由跳转逻辑已调试完成。
但我遇到了异常问题:当处于单个用户的详情页URL下,请求拉取该用户的详细个人信息API时出现异常,具体表现如下,先附上我的相关代码:
相关代码
Home.jsx(首页列表+无限滚动逻辑)
function Home() { const [items, setItems] = useState([]); const [isFetching, setIsFetching] = useState(false); const [page, setPage] = useState(1); const fetchData = (setItems, items) => { let url = `http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/1/50`; axios.get(url).then((res) => { setItems([...items, ...res.data.list]); console.log(res); }); }; const moreData = () => { let url = `http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${page}/50`; axios.get(url).then(res => { setItems([...items, ...res.data.list]); setPage(page+1) setIsFetching(false) }); } const isScrolling =()=>{ if(window.innerHeight + document.documentElement.scrollTop!==document.documentElement.offsetHeight){ return; } setIsFetching(true) } useEffect(()=>{ fetchData(setItems,items); window.addEventListener("scroll", isScrolling); return () => window.removeEventListener("scroll", isScrolling); },[]); useEffect(() => { if (isFetching){ moreData(); } }, [isFetching]); return ( <div className="card-container"> {items.map((user,key) => ( <Card key={key} id={user.id} prefix={user.prefix} name={user.name} lastName={user.lastName} image={user.imageUrl} job={user.title}/> ))} </div> ); }
以上是主路径文件Home.jsx的代码,我在该文件中实现无限滚动逻辑,遍历渲染从API获取的用户数组状态;点击用户卡片后将跳转至Users.jsx对应的详情路由,代码如下:
Users.jsx(用户详情页)
export default function Users() { const params = useParams(); const [users, setUsers] = useState({}) useEffect(()=>{ async function fetchData(){ const res = await axios(`http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}`); console.log(res.data); setUsers(res.data); } fetchData() },[]) return ( <div className='container'> <img src={users.imageUrl}/> <h1 className=''>Info</h1> <div className='card-div'> <div className='name-div'> <h3 style={{fontWeight:"bold", fontSize:"25px"}}>{users.prefix} {users.name} {users.lastName}</h3> <h3 style={{fontStyle:"italic", fontSize:"20px"}}>{users.title}</h3> </div> <div className='details-div'> <h3>Email: {users.email}</h3> <h3>Ip Address: {users.ip}</h3> <h3>Job Area: {users.jobArea}</h3> <h3>Job Type: {users.jobType}</h3> </div> </div> <h1 className='adress-h1'>Address</h1> <div className='address-div'> <h3>{users.company.name} {users.company.suffix}</h3> <h3>City: {users.address.city}</h3> <h3>Country: {users.address.country}</h3> <h3>State: {users.address.state}</h3> <h3>Street Address: {users.address.streetAddress}</h3> <h3>ZIP: {users.address.zipCode}</h3> </div> </div> ) }
异常表现
- 如果我先注释掉
Users.jsx中类名为container的div内的渲染代码,接口请求功能可正常运行,控制台能正常打印返回的数据;此时取消注释,页面可正常渲染并正确展示API返回的用户数据。 - 但如果我在详情页直接刷新页面,页面内容会消失、页面崩溃并抛出多个TypeError。
- 我推测是页面首次渲染时API返回的数据还未加载完成,导致访问不存在的属性引发报错,但不知道具体修复方案。
你的判断没错。
你给users设置的初始值是空对象{},组件第一次渲染的时候接口请求还没返回,这时候直接访问users.company.name、users.address.city这类嵌套属性,等价于从undefined上读取属性——空对象上没有company字段,读出来是undefined,再往下读name直接就抛TypeError,页面直接崩。
之前从首页点进详情页不报错,是开发环境React严格模式会触发两次渲染,第一次报错后第二次渲染时接口数据刚好返回,你没察觉到异常;但直接在详情页刷新,首次渲染必然拿到空对象,直接触发崩溃。
选任意一种即可,推荐组合使用:
- 加加载状态兜底:新增loading状态,请求没完成的时候先展示加载提示,不要直接渲染用户信息。
改Users.jsx的逻辑:export default function Users() { const params = useParams(); // 初始值设为null更方便判断加载状态 const [users, setUsers] = useState(null) const [loading, setLoading] = useState(true) useEffect(()=>{ async function fetchData(){ try { const res = await axios(`http://sweeftdigital-intern.eu-central-1.elasticbeanstalk.com/user/${params.id}`); setUsers(res.data); } catch (err) { console.error('请求失败', err) } finally { // 不管成功失败都关掉loading setLoading(false) } } fetchData() },[params.id]) // 补全params.id依赖,不然用户id变化时不会重新拉取数据 // 加载中兜底 if (loading) return <div>加载中...</div> // 请求失败兜底 if (!users) return <div>用户信息加载失败</div> return ( <div className='container'> <img src={users.imageUrl}/> <h1 className=''>Info</h1> <div className='card-div'> <div className='name-div'> <h3 style={{fontWeight:"bold", fontSize:"25px"}}>{users.prefix} {users.name} {users.lastName}</h3> <h3 style={{fontStyle:"italic", fontSize:"20px"}}>{users.title}</h3> </div> <div className='details-div'> <h3>Email: {users.email}</h3> <h3>Ip Address: {users.ip}</h3> <h3>Job Area: {users.jobArea}</h3> <h3>Job Type: {users.jobType}</h3> </div> </div> <h1 className='adress-h1'>Address</h1> <div className='address-div'> {/* 嵌套属性加可选链兜底,避免后端返回字段缺失时报错 */} <h3>{users.company?.name} {users.company?.suffix}</h3> <h3>City: {users.address?.city}</h3> <h3>Country: {users.address?.country}</h3> <h3>State: {users.address?.state}</h3> <h3>Street Address: {users.address?.streetAddress}</h3> <h3>ZIP: {users.address?.zipCode}</h3> </div> </div> ) } - 用可选链操作符做属性访问兜底:所有访问嵌套属性的地方加
?.,比如users?.company?.name,就算前面的值是undefined也不会报错,只会返回undefined,页面不会崩溃。
额外提醒:你首页的无限滚动逻辑存在闭包问题,moreData方法里拿到的items和page始终是首次渲染的旧值,滚动加载多了会出现重复数据、页码错乱的问题,修复时把setItems和setPage改成函数式更新即可,比如setItems(prev => [...prev, ...res.data.list])、setPage(prev => prev+1),不要直接依赖外部的items、page变量。
内容的提问来源于stack exchange,提问作者ceaiius

