React(Next.js)中H5P实例重复致xAPI事件触发两次求助
H5P-Standalone xAPI事件重复触发的问题与解决办法
问题原因
- 事件监听器未清理:每次
useEffect因依赖变化触发时,都会调用fireCompleteH5PTopic给H5P.externalDispatcher绑定新的xAPI事件监听器,但旧的监听器没有被移除。H5P的externalDispatcher是全局单例,旧实例的监听器会一直留存,导致事件触发时多个监听器同时执行。 - DOM清理不彻底:
removeAllChildNodes仅清除了容器内的DOM节点,但并未处理H5P的全局事件绑定,监听器依然存在于内存中。
解决办法
1. 让事件绑定函数返回清理方法
修改fireCompleteH5PTopic,使其返回用于移除监听器的函数,同时用函数式更新状态避免闭包问题:
const fireCompleteH5PTopic = (H5P) => { const handleXAPIEvent = (event) => { if (event?.data?.statement?.result?.completion) { // 用函数式更新获取最新的counter值 setCounter(prevCounter => prevCounter + 1) completeH5PTopic(event, session.data.user.id, course.slug, topic) } } H5P.externalDispatcher.on("xAPI", handleXAPIEvent); // 返回移除监听器的函数 return () => H5P.externalDispatcher.off("xAPI", handleXAPIEvent); }
2. 在useEffect中添加清理逻辑
利用useEffect的清理函数,在组件卸载或依赖项变化时,移除旧的事件监听器:
useEffect(() => { let cleanupXAPIListener; const initH5p = async (contentLocation) => { const { H5P: H5PStandalone } = require('h5p-standalone') const h5pPath = `https://cdn.thinkeyschool.com/h5p/${contentLocation}` const options = { id: 'THINKeyLesson', h5pJsonPath: h5pPath, frameJs: '/h5p/dist/frame.bundle.js', frameCss: '/h5p/dist/styles/h5p.css', } const element = document.getElementById('h5p_container') removeAllChildNodes(element) await new H5PStandalone(element, options) // 保存清理函数 cleanupXAPIListener = fireCompleteH5PTopic(H5P) setIsLoaderVisible(false) } initH5p(location) // 清理逻辑:在依赖变化或组件卸载时执行 return () => { if (cleanupXAPIListener) { cleanupXAPIListener() } // 可选:如果H5P实例提供销毁方法,可在此调用(如H5P.destroy()),进一步清理资源 } }, [location, session.data.user.id, course.slug, topic])
3. 优化状态更新
将setCounter(counter + 1)改为函数式更新setCounter(prev => prev + 1),避免因闭包捕获旧状态值导致的更新错误。
内容的提问来源于stack exchange,提问作者Diego González Cruz
相关产品推荐
相关产品推荐

