WebXR在React中调用requestSession报错:已有活跃的沉浸式XRSession
问题根因
你的报错核心是React函数组件的渲染机制导致的:
你在组件内部用普通let声明的xrSession、xrRefSpace、gl变量,每次组件触发重渲染(比如调用setXRButton、setInfo修改state的时候)都会被重新初始化为null。当你成功启动AR会话后,修改state触发重渲染,原来保存的会话实例就丢失了,此时再点击按钮判断!xrSession会认为没有活跃会话,重复调用requestSession就触发了已有会话的报错。
修复方案
- 用
useRef存储跨渲染周期需要保留的变量,重渲染时变量值不会被重置 - 把XR初始化逻辑放到
useEffect中,仅在组件挂载时执行一次,避免重复执行 - 组件卸载时清理事件监听和未关闭的AR会话,避免资源残留
修改后的完整代码如下:
import React, { useState, useRef, useEffect } from 'react' const AR = () => { const [xrButton, setXRButton] = useState('XR Not Found') const [info, setInfo] = useState('') const [supported, setSupported] = useState(false) // 用useRef存储跨渲染周期的变量 const xrSession = useRef(null) const xrRefSpace = useRef(null) const gl = useRef(null) const checkInit = useRef(false) const { XRWebGLLayer } = window const checkSupportedState = () => { if(navigator.xr){ navigator.xr.isSessionSupported('immersive-ar').then((isSupported) => { setSupported(isSupported) setXRButton(isSupported ? 'View in AR' : 'AR not found') }) } } const checkXR = () => { if(!checkInit.current){ if (!window.isSecureContext) { document.getElementById("warning") && (document.getElementById("warning").innerText = "WebXR unavailable. Please use secure context") } if (navigator.xr) { navigator.xr.addEventListener('devicechange', checkSupportedState) checkSupportedState() } else { document.getElementById("warning") && (document.getElementById("warning").innerText = "WebXR unavailable for this browser") } checkInit.current = true } } const onSessionEnded = () => { xrSession.current = null setXRButton('Enter AR') setInfo('') gl.current = null } const onXRFrame = (t, frame) => { const session = frame.session session.requestAnimationFrame(onXRFrame) const pose = frame.getViewerPose(xrRefSpace.current) if (!pose) return const pos = pose.transform.position setInfo(`x:${pos.x.toFixed(2)}m y:${pos.y.toFixed(2)}m z:${pos.z.toFixed(2)}m`) } const onSessionStarted = (session) =>{ if (session.domOverlayState) { setInfo('DOM Overlay type: ' + session.domOverlayState.type) } session.addEventListener('end', onSessionEnded) const canvas = document.createElement('canvas') gl.current = canvas.getContext('webgl', { xrCompatible: true }) session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl.current) }) session.requestReferenceSpace('local').then((refSpace) => { xrRefSpace.current = refSpace session.requestAnimationFrame(onXRFrame) }) } const onButtonClicked = () => { if (!xrSession.current) { navigator.xr.requestSession('immersive-ar', { optionalFeatures: ['dom-overlay'], requiredFeatures: ['local'], domOverlay: {root: document.getElementById('overlay')} }).then((session) => { xrSession.current = session setXRButton('Exit AR') onSessionStarted(session) }) } else { xrSession.current.end().then(() => xrSession.current = null) } } // 仅在组件挂载时执行初始化,卸载时清理资源 useEffect(() => { checkXR() return () => { if(navigator.xr) { navigator.xr.removeEventListener('devicechange', checkSupportedState) } if(xrSession.current) { xrSession.current.end() } } }, []) return ( <div id="overlay"> <div className="info-area"> <div id="info">{info}</div> { supported ? <button id="xr-button" onClick={onButtonClicked}>{xrButton}</button> : <button id="xr-button" disabled>{xrButton}</button> } </div> </div> ) } export default AR
内容的提问来源于stack exchange,提问作者Nandita
相关产品推荐
相关产品推荐

