You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

关键修改说明

  1. 跟踪API加载状态:用apiLoadedRef和apiReady状态替代全局回调,确保API加载完成后能立即初始化播放器
  2. 唯一容器ID:给播放器容器ID加上videoId后缀,避免组件多次挂载时出现ID冲突
  3. 动态初始化逻辑:调整初始化播放器的useEffect依赖,包含所有影响播放器配置的参数,确保参数变化或组件重新挂载时能重新创建播放器
  4. 资源清理:在组件卸载时销毁播放器实例,避免内存泄漏

修改后,无论是切换视频ID还是切换组件再返回,播放器都能正常重新渲染和工作。

内容的提问来源于stack exchange,提问作者Feisser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:45:08