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

React模态框关闭后视频引用未销毁致页面卡顿的解决方法

解决React视频模态框内存泄漏问题

问题描述

在React项目中开发视频展示页面,通过Bootstrap模态框结合ReactPlayer播放视频,每次仅允许播放一个。但关闭模态框后切换播放超过3次时,页面卡顿、内存占用持续升高,推测是旧视频引用未销毁导致内存泄漏。

原代码如下:

class VidDisplay extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currvid: "",
      vidref: null,
      vidplaying: false, // 修正原代码语法错误:=改为:
    };
  }

  HandlePlayVidClick = (item) => {
    this.setState({ currvid: item, vidref: React.createRef() });
  };

  render() {
    const {
      vidplaying,
      currvid,
      vidref,
    } = this.state;

    const vidModal = document.getElementById("vidModal");
    const handleHidden = () => {
      this.setState({ vidplaying: false });
    };
    const handleShow = () => {
      this.setState({ vidplaying: true });
    };

    if (vidModal) {
      vidModal.addEventListener("hidden.bs.modal", handleHidden);
    }

    if (vidModal) {
      vidModal.addEventListener("show.bs.modal", handleShow);
    }

    return (
      <>
        {/* 视频列表渲染逻辑 */}
        {val?.data.length ? (
          val?.data.map((elm, i) => (
            <div key={i}>
              <Link
                href="#"
                data-bs-toggle="modal"
                data-bs-target="#vidModal"
                data-bs-videolink={elm.full_video}
                onClick={() => this.HandlePlayVidClick(elm.full_video)}
              >
                preview
              </Link>
            </div>
          ))
        ) : null}

        {/* 模态框代码 */}
        <div className="modal fade profile-modal" id="vidModal">
          <div className="modal-dialog">
            <div className="modal-content">
              <div className="modal-header">
                <button
                  type="button"
                  className="btn-close"
                  data-bs-dismiss="modal"
                ></button>
              </div>
              {vidplaying && (
                <div className="modal-body">
                  <ReactPlayer
                    url={`${process.env.REACT_APP_VIDEO_URL}${currvid}`}
                    ref={vidref}
                    className="react-player"
                    playsinline={true}
                    muted={true}
                    controls={true}
                    playing={vidplaying}
                    width="100%"
                  />
                </div>
              )}
            </div>
          </div>
        </div>
      </>
    )
  }
}

核心问题分析

  1. 重复绑定事件监听:render方法每次执行都会给模态框添加事件监听,导致回调被多次绑定,旧监听无法销毁,引发内存泄漏。
  2. 视频资源未主动清理:关闭模态框仅修改vidplaying状态,未主动停止视频播放或释放底层视频元素资源。
  3. Ref管理混乱:每次点击播放都创建新的vidref,旧引用残留无法被垃圾回收。

解决方案

1. 生命周期内绑定/解绑事件

将模态框事件监听移到组件生命周期方法中,避免重复绑定,并在组件卸载时清理:

componentDidMount() {
  this.vidModal = document.getElementById("vidModal");
  if (this.vidModal) {
    this.vidModal.addEventListener("hidden.bs.modal", this.handleHidden);
    this.vidModal.addEventListener("show.bs.modal", this.handleShow);
  }
}

componentWillUnmount() {
  if (this.vidModal) {
    this.vidModal.removeEventListener("hidden.bs.modal", this.handleHidden);
    this.vidModal.removeEventListener("show.bs.modal", this.handleShow);
  }
  // 组件卸载时强制清理视频资源
  if (this.vidref.current) {
    this.vidref.current.stop();
    const videoElement = this.vidref.current.getInternalPlayer();
    if (videoElement) videoElement.src = '';
  }
}

2. 复用单个Ref实例

不在每次点击时创建新Ref,直接在构造函数中初始化一次:

constructor(props) {
  super(props);
  this.state = {
    currvid: "",
    vidplaying: false,
  };
  this.vidref = React.createRef(); // 仅初始化一次
}

HandlePlayVidClick = (item) => {
  this.setState({ currvid: item }); // 不再创建新Ref
};

3. 模态框关闭时彻底清理视频资源

在hidden.bs.modal回调中,主动停止播放并清空视频元素的src,强制释放资源:

handleHidden = () => {
  if (this.vidref.current) {
    this.vidref.current.stop();
    const videoElement = this.vidref.current.getInternalPlayer();
    if (videoElement) {
      videoElement.src = '';
      videoElement.load(); // 触发浏览器资源回收
    }
  }
  this.setState({ vidplaying: false, currvid: "" }); // 清空视频地址
};

handleShow = () => {
  this.setState({ vidplaying: true });
};

4. 优化条件渲染逻辑

确保ReactPlayer仅在vidplaying和currvid都存在时才渲染,避免无效实例残留:

{vidplaying && currvid && (
  <div className="modal-body">
    <ReactPlayer
      url={`${process.env.REACT_APP_VIDEO_URL}${currvid}`}
      ref={this.vidref}
      className="react-player"
      playsinline={true}
      muted={true}
      controls={true}
      playing={vidplaying}
      width="100%"
    />
  </div>
)}

修改后的完整代码

class VidDisplay extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currvid: "",
      vidplaying: false,
    };
    this.vidref = React.createRef();
    this.vidModal = null;
  }

  componentDidMount() {
    this.vidModal = document.getElementById("vidModal");
    if (this.vidModal) {
      this.vidModal.addEventListener("hidden.bs.modal", this.handleHidden);
      this.vidModal.addEventListener("show.bs.modal", this.handleShow);
    }
  }

  componentWillUnmount() {
    if (this.vidModal) {
      this.vidModal.removeEventListener("hidden.bs.modal", this.handleHidden);
      this.vidModal.removeEventListener("show.bs.modal", this.handleShow);
    }
    if (this.vidref.current) {
      this.vidref.current.stop();
      const videoElement = this.vidref.current.getInternalPlayer();
      if (videoElement) videoElement.src = '';
    }
  }

  HandlePlayVidClick = (item) => {
    this.setState({ currvid: item });
  };

  handleHidden = () => {
    if (this.vidref.current) {
      this.vidref.current.stop();
      const videoElement = this.vidref.current.getInternalPlayer();
      if (videoElement) {
        videoElement.src = '';
        videoElement.load();
      }
    }
    this.setState({ vidplaying: false, currvid: "" });
  };

  handleShow = () => {
    this.setState({ vidplaying: true });
  };

  render() {
    const { vidplaying, currvid } = this.state;

    return (
      <>
        {val?.data.length ? (
          val.data.map((elm, i) => (
            <div key={i}>
              <Link
                href="#"
                data-bs-toggle="modal"
                data-bs-target="#vidModal"
                data-bs-videolink={elm.full_video}
                onClick={() => this.HandlePlayVidClick(elm.full_video)}
              >
                preview
              </Link>
            </div>
          ))
        ) : null}

        <div className="modal fade profile-modal" id="vidModal">
          <div className="modal-dialog">
            <div className="modal-content">
              <div className="modal-header">
                <button
                  type="button"
                  className="btn-close"
                  data-bs-dismiss="modal"
                ></button>
              </div>
              {vidplaying && currvid && (
                <div className="modal-body">
                  <ReactPlayer
                    url={`${process.env.REACT_APP_VIDEO_URL}${currvid}`}
                    ref={this.vidref}
                    className="react-player"
                    playsinline={true}
                    muted={true}
                    controls={true}
                    playing={vidplaying}
                    width="100%"
                  />
                </div>
              )}
            </div>
          </div>
        </div>
      </>
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 06:32:23