React TypeScript中YouTube播放器组件切换后无法重渲染问题
解决React中Youtube播放器组件切换后无法重新渲染的问题
问题描述
我基于React实现了一个Youtube播放器组件PlayerFrame,并在类组件Player中调用它。首次进入Player组件时,播放器能正常加载播放,但切换到其他组件再返回后,播放器就无法重新渲染了。调整过销毁逻辑后,切换视频ID时播放器不会被销毁,但组件卸载再挂载的问题依旧存在。
核心原因
问题出在Youtube API的全局回调window.onYouTubeIframeAPIReady上:这个回调只会在API脚本第一次加载完成时触发一次。当组件卸载后重新挂载,API脚本已经存在于页面中,不会再触发该回调,导致新的播放器实例无法创建。同时,原代码中初始化播放器的useEffect依赖为空数组,组件重新挂载时不会重新执行初始化逻辑。
修复方案
修改PlayerFrame的逻辑,不再依赖全局回调,而是直接检测API加载状态;同时调整useEffect依赖,确保组件重新挂载时能重新初始化播放器。
修改后的PlayerFrame代码
declare global { interface Window { YT: any; onYouTubeIframeAPIReady?: () => void; } } import React, { useEffect, useRef, useState } from 'react'; interface PlayerFrameProps { videoId: string; width: string; height: string; onStateChange?: () => void; } const PlayerFrame: React.FC<PlayerFrameProps> = ({ videoId, width, height, onStateChange }) => { const playerRef = useRef<any>(null); const apiLoadedRef = useRef<boolean>(false); const [apiReady, setApiReady] = useState<boolean>(false); useEffect(() => { const loadYouTubeAPI = () => { // 检测API是否已加载,避免重复插入脚本 if (apiLoadedRef.current || window.YT) { apiLoadedRef.current = true; setApiReady(true); return; } const tag = document.createElement('script'); tag.id = 'youtube-api'; tag.src = "https://www.youtube.com/iframe_api"; // 脚本加载完成后标记API就绪 tag.onload = () => { apiLoadedRef.current = true; setApiReady(true); }; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode?.insertBefore(tag, firstScriptTag); }; loadYouTubeAPI(); }, []); useEffect(() => { if (!apiReady || !window.YT) return; // 销毁旧播放器实例(如果存在) if (playerRef.current) { playerRef.current.destroy(); playerRef.current = null; } // 创建新的播放器实例,用videoId生成唯一容器ID避免冲突 playerRef.current = new window.YT.Player(`player-${videoId}`, { height: height, width: width, videoId: videoId, events: { 'onReady': (event: any) => { event.target.playVideo(); }, 'onStateChange': (event: any) => { if (event.data === window.YT.PlayerState.ENDED) { onStateChange?.(); } } } }); // 组件卸载时清理播放器资源 return () => { if (playerRef.current) { playerRef.current.destroy(); playerRef.current = null; } }; }, [apiReady, videoId, width, height, onStateChange]); return ( <div> <div id={`player-${videoId}`}></div> </div> ); }; export default PlayerFrame;
关键修改说明
- 跟踪API加载状态:用
apiLoadedRef和apiReady状态替代全局回调,确保API加载完成后能立即初始化播放器 - 唯一容器ID:给播放器容器ID加上
videoId后缀,避免组件多次挂载时出现ID冲突 - 动态初始化逻辑:调整初始化播放器的
useEffect依赖,包含所有影响播放器配置的参数,确保参数变化或组件重新挂载时能重新创建播放器 - 资源清理:在组件卸载时销毁播放器实例,避免内存泄漏
修改后,无论是切换视频ID还是切换组件再返回,播放器都能正常重新渲染和工作。
内容的提问来源于stack exchange,提问作者Feisser
相关产品推荐
相关产品推荐

