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> </> ) } }
核心问题分析
- 重复绑定事件监听:
render方法每次执行都会给模态框添加事件监听,导致回调被多次绑定,旧监听无法销毁,引发内存泄漏。 - 视频资源未主动清理:关闭模态框仅修改
vidplaying状态,未主动停止视频播放或释放底层视频元素资源。 - 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
相关产品推荐
相关产品推荐

