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

React useRef报Cannot set properties of null错误解决方案

问题根因

这个报错是运行时真实触发的空值访问,和TS类型校验无关。代码里的!非空断言仅能骗过TS类型检查,不会做任何运行时的空值兜底。
出现「视频能正常展示但控制台抛错」的核心原因是React 18及以上版本开发环境默认开启的严格模式(Strict Mode)机制:

  • 严格模式下组件首次加载时会完整执行一次挂载流程,紧接着触发卸载、再重新挂载一次,用来提前暴露代码里的副作用、清理逻辑问题
  • 第一次执行useEffect中的getDeviceMedia方法时,navigator.mediaDevices.getUserMedia是异步操作,等权限申请通过、拿到媒体流的时候,组件已经被严格模式触发的卸载流程销毁,此时videoRef.current会被React重置为null,对null赋值srcObject就会抛出你看到的TypeError
  • 等组件第二次重新挂载完成后,getDeviceMedia再次执行拿到流,此时ref已经正常绑定到DOM上的video元素,赋值成功,所以视频功能可以正常运行

除此之外当前实现还有两个必现的隐藏问题:

  • 没有做ref空值兜底,生产环境如果遇到组件提前卸载、video元素被条件渲染隐藏的场景,同样会触发这个空值报错
  • 没有在组件卸载时关闭媒体流,会导致摄像头一直被页面占用,用户设备摄像头指示灯常亮,造成资源泄漏

修复方案

不要用非空断言跳过校验,按以下逻辑修改即可完全解决问题:

  1. 所有访问videoRef.current的位置先做非空判断,从根源避免空值赋值
  2. 增加卸载标记,组件卸载后不再执行后续的状态更新、ref赋值操作
  3. 增加副作用清理函数,组件卸载时主动关闭所有媒体轨道,释放摄像头资源
  4. (可选)把媒体流赋值给video元素的逻辑拆成独立的副作用,依赖mediaStream状态,保证赋值时DOM和流都已就绪

完整修复后的代码:

const Page: React.FC = () => {
  const videoRef = useRef<HTMLVideoElement | null>(null)
  const [mediaStream, setMediaStream] = useState<MediaStream | null>(null)

  useEffect(() => {
    let isUnmounted = false
    const getDeviceMedia = async () => {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true })
        // 组件已卸载时直接释放流,不做后续操作
        if (isUnmounted) {
          stream.getTracks().forEach(track => track.stop())
          return
        }
        setMediaStream(stream)
      } catch (err) {
        // 可在此处补充用户拒绝摄像头权限的兜底提示
        console.error('摄像头权限获取失败', err)
      }
    }
    
    getDeviceMedia()

    // 组件卸载时执行清理
    return () => {
      isUnmounted = true
      if (mediaStream) {
        mediaStream.getTracks().forEach(track => track.stop())
      }
    }
  }, [])

  // 监听媒体流变化,流就绪且ref存在时再赋值
  useEffect(() => {
    if (videoRef.current && mediaStream) {
      videoRef.current.srcObject = mediaStream
    }
  }, [mediaStream])

  return <video ref={videoRef} autoPlay muted />
}

补充说明
  • 如果不想拆分两个useEffect,也可以在原赋值逻辑前加一层空值判断:if (videoRef.current) videoRef.current.srcObject = stream,同样可以解决报错,只是拆分副作用的写法时序更稳定,不会因为异步操作的先后顺序踩坑
  • 不需要为了规避这个报错关闭React严格模式,严格模式的重复挂载逻辑本质是帮你提前发现代码里缺失清理逻辑、时序不对的问题,这些问题在生产环境遇到路由切换、条件渲染时一样会出现
  • 生产环境构建后会默认关闭严格模式的重复挂载行为,但空值兜底、媒体流清理的逻辑必须保留,否则会引发线上报错和设备资源泄漏

内容的提问来源于stack exchange,提问作者G. Hruschka

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 04:09:41