React组件中为HTML5 Video标签添加ended事件报错排查
解决React视频组件中
addEventListener报错的问题 看起来你遇到的核心问题是:在componentDidMount里尝试获取视频元素的ref时,这个元素还没被渲染到DOM中,所以this.refs.introVideo返回undefined,调用addEventListener自然会报错。
为什么会出现这个问题?
你的视频元素被包裹在Dialog组件里,而Dialog的显示由this.props.isOpen控制。如果组件挂载时isOpen是false,那么Dialog及其内部的video元素根本不会被渲染,此时ref自然拿不到对应的DOM节点。另外,你使用的是React官方已经废弃的字符串ref,这种方式不仅不被推荐,也更容易出现无法准确获取元素的问题。
修复方案
这里给你两种可行的解决办法,你可以根据需求选择:
方法1:改用React.createRef()(推荐)+ 在componentDidUpdate中监听状态变化
先替换旧的字符串ref,使用类组件推荐的createRef,然后在组件更新时检查isOpen的状态变化,确保视频元素存在后再添加事件监听:
import React from 'react'; export default class ShowMovieWindow extends React.Component { constructor(props) { super(props); this.handleVideoEnd = this.handleVideoEnd.bind(this); // 创建ref实例 this.introVideoRef = React.createRef(); }; handleVideoEnd() { console.log("Finished"); // 在这里执行隐藏视频或其他后续操作 } componentDidMount() { // 组件挂载时如果isOpen已经为true,直接添加监听 if (this.props.isOpen && this.introVideoRef.current) { this.introVideoRef.current.addEventListener("ended", this.handleVideoEnd); } } componentDidUpdate(prevProps) { // 当isOpen从false变为true时,添加事件监听 if (!prevProps.isOpen && this.props.isOpen && this.introVideoRef.current) { this.introVideoRef.current.addEventListener("ended", this.handleVideoEnd); } // 当isOpen从true变为false时,移除监听(避免内存泄漏) if (prevProps.isOpen && !this.props.isOpen && this.introVideoRef.current) { this.introVideoRef.current.removeEventListener("ended", this.handleVideoEnd); } } componentWillUnmount() { // 组件卸载前务必移除监听,防止内存泄漏 if (this.introVideoRef.current) { this.introVideoRef.current.removeEventListener("ended", this.handleVideoEnd); } } render() { return ( <Dialog isOpen={this.props.isOpen}> <div className={Classes.DIALOG_BODY}> <div> {/* 使用createRef绑定视频元素 */} <video ref={this.introVideoRef} autoPlay> <source src={'./asset/video/intro.mp4'} type="video/mp4"/> </video> </div> </div> </Dialog> ); } }
方法2:利用Dialog的onEntered回调(如果你的Dialog组件支持)
很多UI库的Dialog组件(比如Material-UI的Dialog)都提供了onEntered回调,这个回调会在Dialog的进入动画完成、组件完全渲染到DOM后触发,此时视频元素肯定已经存在,直接在这里添加监听即可:
import React from 'react'; export default class ShowMovieWindow extends React.Component { constructor(props) { super(props); this.handleVideoEnd = this.handleVideoEnd.bind(this); this.introVideoRef = React.createRef(); }; handleVideoEnd() { console.log("Finished"); } // Dialog完全渲染完成后触发 handleDialogEntered = () => { if (this.introVideoRef.current) { this.introVideoRef.current.addEventListener("ended", this.handleVideoEnd); } } // Dialog关闭动画完成后触发,移除监听 handleDialogExited = () => { if (this.introVideoRef.current) { this.introVideoRef.current.removeEventListener("ended", this.handleVideoEnd); } } componentWillUnmount() { if (this.introVideoRef.current) { this.introVideoRef.current.removeEventListener("ended", this.handleVideoEnd); } } render() { return ( <Dialog isOpen={this.props.isOpen} onEntered={this.handleDialogEntered} onExited={this.handleDialogExited} > <div className={Classes.DIALOG_BODY}> <div> <video ref={this.introVideoRef} autoPlay> <source src={'./asset/video/intro.mp4'} type="video/mp4"/> </video> </div> </div> </Dialog> ); } }
额外注意事项
- 一定要在组件卸载或者Dialog关闭时移除事件监听,避免内存泄漏。
- 尽量不要再使用字符串ref,React官方已明确表示该方式会在未来版本中移除,推荐使用
createRef(类组件)或useRef(函数组件)。
内容的提问来源于stack exchange,提问作者NiBE
相关产品推荐
相关产品推荐

