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

WebXR在React中调用requestSession报错:已有活跃的沉浸式XRSession

问题根因

你的报错核心是React函数组件的渲染机制导致的:
你在组件内部用普通let声明的xrSession、xrRefSpace、gl变量,每次组件触发重渲染(比如调用setXRButton、setInfo修改state的时候)都会被重新初始化为null。当你成功启动AR会话后,修改state触发重渲染,原来保存的会话实例就丢失了,此时再点击按钮判断!xrSession会认为没有活跃会话,重复调用requestSession就触发了已有会话的报错。

修复方案

  1. 用useRef存储跨渲染周期需要保留的变量,重渲染时变量值不会被重置
  2. 把XR初始化逻辑放到useEffect中,仅在组件挂载时执行一次,避免重复执行
  3. 组件卸载时清理事件监听和未关闭的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 21:24:03