React电影应用跳转详情页时渲染失败并报错的问题
React电影详情页加载报错解决方案
错误原因
- 初始渲染时,
movieDetailData是空对象,直接访问description.length、genres[0].name等属性会因值为undefined抛出TypeError。 movieTrailerData初始为空数组,访问movieTrailerData[0].title同样会触发报错。- 热重载时组件重新渲染,此时数据已加载完成,所以注释再取消注释能正常显示;但刷新页面后数据重新请求,初始状态回到空值,问题复现。
解决方案
方案一:父组件添加加载状态控制
在请求数据完成前,显示加载指示器,避免渲染未准备好的数据:
import React, {useState, useEffect} from "react"; import {Box, CircularProgress} from "@mui/material"; import {useParams} from "react-router-dom"; import MovieTrailers from "../components/MovieTrailers"; import Details from "../components/Details"; import {fetchData, options} from "../utils/fetchData"; function MovieDetails() { const [movieDetailData, setMovieDetailData] = useState({}); const [movieTrailerData, setMovieTrailerData] = useState([]); const [isLoading, setIsLoading] = useState(true); const {id} = useParams(); useEffect(()=>{ const fetchMovieData = async ()=> { setIsLoading(true); try { const detailData = await fetchData(`https://movies-app1.p.rapidapi.com/api/movie/${id}`, options); setMovieDetailData(detailData.result); const trailerData = await fetchData(`https://movies-app1.p.rapidapi.com/api/trailers/${id}`, options); setMovieTrailerData(trailerData.result); } catch (err) { console.error("请求数据失败:", err); } finally { setIsLoading(false); } } fetchMovieData(); },[id]); if(isLoading) { return <Box display="flex" justifyContent="center" alignItems="center" minHeight="80vh"> <CircularProgress /> </Box>; } return( <Box> <Details movieDetailData={movieDetailData} /> <MovieTrailers movieTrailerData={movieTrailerData} /> </Box>) } export default MovieDetails;
方案二:子组件添加空值安全访问
使用可选链操作符(?.)和空值合并运算符(??),处理未定义的属性和空数组情况:
修改Details组件
import React from "react"; import {Box, Stack, Typography, Chip} from "@mui/material"; function Details(props) { const{image, titleOriginal, countries, genres, rating, release, description} = props.movieDetailData; return <Stack direction="row" width="100%" sx={{backgroundColor:"#fff"}}> {image && <img className="movie-poster" src={image} alt="movie-poster"/>} <Stack p="50px" sx={{width:"60%"}}> <Typography variant="h2" fontWeight="700"> {titleOriginal ?? "暂无标题"} </Typography> <Box pt="30px" sx={{display:"flex", gap:"30px"}}> <Chip label={<Typography variant="h6" fontWeight="700">{`rating : ${rating ?? "暂无评分"}`}</Typography>} /> <Chip label={<Typography variant="h6" fontWeight="700">{`release: ${release ?? "暂无上映信息"}`}</Typography>} /> </Box> <Typography variant="h6" pt="30px"> {description?.length>600 ? description.substring(0, 601) + "..." : description ?? "暂无简介"} </Typography> <Box pt="30px" sx={{display:"flex", gap:"30px"}}> <Chip label={<Typography variant="h6" fontWeight="700">{`genre : ${genres?.[0]?.name ?? "暂无类型"}`}</Typography>} /> <Chip label={<Typography variant="h6" fontWeight="700">{`origin : ${countries?.[0]?.name ?? "暂无产地"}`}</Typography>} /> </Box> </Stack> </Stack> } export default Details;
修改MovieTrailers组件
import React from "react"; import {Box, Stack, Typography} from "@mui/material"; import TrailerCard from "./TrailerCard"; function MovieTrailers({movieTrailerData}) { return <Box p="30px"> <Typography variant="h4" pt="40px" pb="50px" fontWeight="700" m="auto"> {`Watch ${movieTrailerData?.[0]?.title ?? "相关预告片"}`} </Typography> <Stack direction="row" justifyContent="space-between" flexWrap="wrap" gap="20px"> {(movieTrailerData?.slice(0, 3) || []).map((trailer)=>( <TrailerCard key={trailer.url} trailer={trailer}/> ))} </Stack> </Box> } export default MovieTrailers;
修改TrailerCard组件
import React from "react"; import {Box, Stack, Typography, Chip} from "@mui/material"; function TrailerCard({trailer}) { const{thumbnail, ago, author, views, title, url, description} = trailer; return <Box className="trailer-card" p="10px" backgroundColor="#fff" width="25%"> <Stack> <a className="movie-trailer-link" href={url} target="_blank" rel="noreferrer"> {thumbnail && <img className="movie-trailer-thumbnail" src={thumbnail} alt="movie-trailer"/>} <Typography pt="10px" variant="body1" fontWeight="700" > {title ?? "暂无标题"} </Typography> <Stack paddingBlock="10px" direction="row" justifyContent="space-between" gap="10px" flexWrap="wrap"> <Chip label={`views: ${views ?? "0"}`} /> <Chip label={`ago: ${ago ?? "未知"}`}/> </Stack> <Typography variant="subtitle2">{`YT: ${author?.name ?? "未知作者"}`}</Typography> <Typography variant="body2" pb="20px"> {description ?? "暂无描述"} </Typography> </a> </Stack> </Box> } export default TrailerCard;
关键优化点
- 加载状态:通过
isLoading控制渲染逻辑,避免在数据未就绪时渲染子组件。 - 可选链
?.:安全访问嵌套属性,防止因属性不存在抛出错误。 - 空值合并
??:为未定义的属性提供默认值,提升用户体验。 - 唯一
key:在列表循环中添加唯一标识,符合React渲染规范。
内容的提问来源于stack exchange,提问作者AKHIL PANWAR
相关产品推荐
相关产品推荐

