组件挂载时修改Context中URL加载Youtube视频,视频状态异常排查
问题描述
我尝试用React Context复用逻辑,首页通过Youtube API加载全量视频,搜索页加载过滤后的视频。但当前代码中videos状态始终为undefined,无法正常渲染视频组件。相关代码如下:
Context代码
import { createContext, useState, useEffect } from "react"; import { IVideo } from "../@types/video"; import axios from "axios"; import moment from "moment"; import { IContextProps, IVideoContext } from "../@types/context"; export const VideoContext = createContext<IVideoContext>({} as IVideoContext); export const VideoData = ({ children }: IContextProps) => { const [videos, setVideos] = useState<IVideo[]>([]); // videos状态返回undefined const [url, setUrl] = useState(``) // url状态在console.log中显示正确值 const [dependency, setDependency] = useState(``) // 这个状态值也正确 useEffect(() => { load() // 这个函数似乎没生效 }, [dependency]) async function load() { try { const response = await axios.get(url) // 手动写url的话这段代码能正常工作 setVideos(response.data.items) }catch(error){ console.log(error) } } function formatViewCount(viewCount: number): string { if (viewCount >= 1000000) { return `${(viewCount / 1000000).toFixed(0)} mi de visualizações`; } else if (viewCount >= 1000) { return `${(viewCount / 1000).toFixed(0)} mil visualizações`; } else { return `${viewCount} visualizações`; } } function getPublishedTime(publishedAt: string) { const now = moment(); const publishedTime = moment(publishedAt); const diffDays = now.diff(publishedTime, 'days'); if (diffDays <= 0) { return 'hoje'; } else if (diffDays === 1) { return 'há 1 dia'; } else if (diffDays <= 7) { return `há ${diffDays} dias`; } else if (diffDays <= 30) { const diffWeeks = Math.floor(diffDays / 7); if (diffWeeks === 1) { return 'há 1 semana'; } else { return `há ${diffWeeks} semanas`; } } else if (diffDays <= 365) { const diffMonths = Math.floor(diffDays / 30); if (diffMonths === 1) { return 'há 1 mês'; } else { return `há ${diffMonths} meses`; } } else { const diffYears = Math.floor(diffDays / 365); if (diffYears === 1) { return 'há 1 ano'; } else { return `há ${diffYears} anos`; } } } return ( <VideoContext.Provider value={{ videos, getPublishedTime, formatViewCount, setUrl, setDependency }}> {children} </VideoContext.Provider> ) }
首页代码
import { useContext, useEffect } from "react"; import VideoComponent from "../../components/videoComponent"; import { Container } from "./styles"; import { VideoContext } from "../../context/videoContext"; import { IVideo } from "../../@types/video"; import { useCategoryContext } from "../../context/categoriesContext"; function Home(){ const {categoryId} = useCategoryContext() const { videos, setUrl, setDependency } = useContext(VideoContext) const API_KEY = 'AIzaSyBgyeOAGzLblKVCjEI-soVVLkQdNfvApkA' useEffect(() => { setUrl(`https://youtube.googleapis.com/youtube/v3/videos?part=snippet&part=statistics&chart=mostPopular&maxResults=48&videoCategoryId=${categoryId}&key=${API_KEY}`); setDependency(categoryId) }, []) return ( <Container> {videos.map((video: IVideo) => ( <VideoComponent video={video} key={video.id} /> ))} </Container> ) } export default Home;
视频组件代码
import { useContext } from "react"; import { IVideos } from "../../@types/video"; import { ChannelImage, Container, ImageBanner, TextCard, TextContainer, Title, TitleContainer } from "./styles"; import { VideoContext } from "../../context/videoContext"; function VideoComponent({video}:IVideos){ const { formatViewCount, getPublishedTime } = useContext(VideoContext) return ( <Container> <ImageBanner src={video.snippet.thumbnails.maxres?.url || video.snippet.thumbnails.high?.url} /> <TitleContainer> <ChannelImage>{video.snippet.channelTitle.charAt(0).toUpperCase()}</ChannelImage> <TextContainer> <Title>{video.snippet.title}</Title> <TextCard>{video.snippet.channelTitle}</TextCard> <TextCard>{`${formatViewCount(Number(video.statistics.viewCount))} - ${getPublishedTime(video.snippet.publishedAt)}`}</TextCard> </TextContainer> </TitleContainer> </Container> ) } export default VideoComponent;
错误原因排查
状态更新竞态问题:Home组件的
useEffect中同时调用setUrl和setDependency,但React的状态更新是异步批量处理的。当dependency变化触发Context中的useEffect时,url可能还没完成更新,导致load函数使用空字符串请求API,请求失败后setVideos未执行,或返回数据异常导致videos变为undefined。冗余的dependency状态:额外维护
dependency状态完全没必要,直接监听url的变化就能触发数据加载,多一层状态反而增加了同步风险。未处理API返回的边界情况:如果API请求失败或返回的
response.data.items为undefined,setVideos会直接把videos设为undefined,而不是保持初始的空数组。Home组件的useEffect依赖缺失:当前
useEffect的依赖数组为空,当categoryId变化时不会重新请求数据,同时首次渲染时也无法保证categoryId已经获取到有效值。
修复方案
1. 修改Context代码,移除冗余状态并优化加载逻辑
import { createContext, useState, useEffect } from "react"; import { IVideo } from "../@types/video"; import axios from "axios"; import moment from "moment"; import { IContextProps, IVideoContext } from "../@types/context"; export const VideoContext = createContext<IVideoContext>({} as IVideoContext); export const VideoData = ({ children }: IContextProps) => { const [videos, setVideos] = useState<IVideo[]>([]); const [url, setUrl] = useState(``); const [isLoading, setIsLoading] = useState(false); // 添加加载状态 // 直接监听url变化,移除dependency状态 useEffect(() => { if (!url) return; // 空url不执行请求 load(); }, [url]) async function load() { setIsLoading(true); try { const response = await axios.get(url); // 处理items可能为undefined的情况,确保始终是数组 setVideos(response.data.items || []); } catch(error){ console.log(error); setVideos([]); // 出错时重置为空数组 } finally { setIsLoading(false); } } // 原有的formatViewCount和getPublishedTime函数保持不变 function formatViewCount(viewCount: number): string { if (viewCount >= 1000000) { return `${(viewCount / 1000000).toFixed(0)} mi de visualizações`; } else if (viewCount >= 1000) { return `${(viewCount / 1000).toFixed(0)} mil visualizações`; } else { return `${viewCount} visualizações`; } } function getPublishedTime(publishedAt: string) { const now = moment(); const publishedTime = moment(publishedAt); const diffDays = now.diff(publishedTime, 'days'); if (diffDays <= 0) { return 'hoje'; } else if (diffDays === 1) { return 'há 1 dia'; } else if (diffDays <= 7) { return `há ${diffDays} dias`; } else if (diffDays <= 30) { const diffWeeks = Math.floor(diffDays / 7); if (diffWeeks === 1) { return 'há 1 semana'; } else { return `há ${diffWeeks} semanas`; } } else if (diffDays <= 365) { const diffMonths = Math.floor(diffDays / 30); if (diffMonths === 1) { return 'há 1 mês'; } else { return `há ${diffMonths} meses`; } } else { const diffYears = Math.floor(diffDays / 365); if (diffYears === 1) { return 'há 1 ano'; } else { return `há ${diffYears} anos`; } } } return ( <VideoContext.Provider value={{ videos, isLoading, getPublishedTime, formatViewCount, setUrl }}> {children} </VideoContext.Provider> ) }
2. 修改Home组件,完善依赖并处理加载状态
import { useContext, useEffect } from "react"; import VideoComponent from "../../components/videoComponent"; import { Container } from "./styles"; import { VideoContext } from "../../context/videoContext"; import { IVideo } from "../../@types/video"; import { useCategoryContext } from "../../context/categoriesContext"; function Home(){ const {categoryId} = useCategoryContext() const { videos, isLoading, setUrl } = useContext(VideoContext) const API_KEY = 'AIzaSyBgyeOAGzLblKVCjEI-soVVLkQdNfvApkA' // 添加categoryId到依赖数组,分类变化时重新请求 useEffect(() => { if (!categoryId) return; // 确保categoryId有效再设置url setUrl(`https://youtube.googleapis.com/youtube/v3/videos?part=snippet&part=statistics&chart=mostPopular&maxResults=48&videoCategoryId=${categoryId}&key=${API_KEY}`); }, [categoryId]) if (isLoading) { return <div>加载中...</div>; // 加载状态提示 } return ( <Container> {videos.map((video: IVideo) => ( <VideoComponent video={video} key={video.id} /> ))} </Container> ) } export default Home;
3. 类型定义校验(可选)
确保IVideoContext的类型定义正确,避免videos出现undefined:
// 示例类型定义 export interface IVideoContext { videos: IVideo[]; isLoading: boolean; getPublishedTime: (publishedAt: string) => string; formatViewCount: (viewCount: number) => string; setUrl: (url: string) => void; }
内容的提问来源于stack exchange,提问作者Leon Murelli SIlveira
相关产品推荐
相关产品推荐

