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

Next.js路由跳转后Video ref不渲染、函数组件无法赋值ref问题

问题根因

1. 客户端导航下的ref挂载时序错位

你全局Socket上下文是在应用根节点(_app.tsx/root layout)初始化的,上下文中的myVideo、userVideo ref在应用首次启动时就完成了创建。

  • 刷新进入CallPage时属于全页加载,挂载顺序为:上下文初始化创建ref → CallPage挂载 → VideoPlayer内的video元素绑定ref到上下文的ref对象 → 后续socket回调拿到音视频流赋值给ref.current.srcObject,流程完全正常。
  • 从首页通过Link客户端跳转时,Socket上下文早已完成初始化,甚至部分socket回调(比如接收远端流的事件监听)已经提前绑定完毕。此时VideoPlayer挂载、video元素绑定ref的动作晚于socket流回调的触发时机,回调执行时ref.current还是null,流无法被挂载到video元素上,自然没有画面。

2. 报错的真实触发原因

你看到的function components cannot be given refs报错大概率是跳转时的遗留报错,或者你在传递ref时的隐性错误:

  • 如果你是把ref作为普通prop传给VideoPlayer(比如<VideoPlayer myVideoRef={myVideo} />),然后在组件内绑定到原生video,确实不需要forwardRef。但如果你的代码里存在直接给VideoPlayer组件加ref属性的写法(比如<VideoPlayer ref={myVideo} />),就会触发该报错:哪怕你组件内部是绑定到原生DOM上的,只要给函数组件本身加ref且未配置forwardRef就会抛出该错误。
  • 部分Next.js客户端导航时,旧页面卸载的ref清理逻辑和新页面挂载的逻辑冲突,也会偶发该类报错,不影响核心问题的解决。
解决方案

1. 调整音视频流挂载逻辑,不直接依赖全局ref的实时值

不要在Socket上下文的事件回调里直接操作ref.current,改为把音视频流存在上下文的state里,在VideoPlayer组件内部监听流的变化,主动赋值给本地绑定的video元素。
示例代码:

// Socket上下文代码
const SocketProvider = ({children}) => {
  const [localStream, setLocalStream] = useState<MediaStream | null>(null)
  const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null)

  // 初始化本地流的逻辑
  const initLocalStream = async () => {
    const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true})
    setLocalStream(stream)
  }

  // 接收远端流的socket回调
  useEffect(() => {
    socket.on('remote-stream', (stream) => {
      setRemoteStream(stream)
    })
  }, [socket])

  return <SocketContext.Provider value={{localStream, remoteStream, initLocalStream}}>{children}</SocketContext.Provider>
}
// VideoPlayer组件代码
const VideoPlayer = () => {
  const {localStream, remoteStream} = useSocket()
  const myVideoRef = useRef<HTMLVideoElement>(null)
  const userVideoRef = useRef<HTMLVideoElement>(null)

  // 本地流挂载
  useEffect(() => {
    if (myVideoRef.current && localStream) {
      myVideoRef.current.srcObject = localStream
    }
  }, [localStream])

  // 远端流挂载
  useEffect(() => {
    if (userVideoRef.current && remoteStream) {
      userVideoRef.current.srcObject = remoteStream
    }
  }, [remoteStream])

  return (
    <div>
      <video ref={myVideoRef} autoPlay muted playsInline />
      <video ref={userVideoRef} autoPlay playsInline />
    </div>
  )
}

2. 修复ref传递的报错

检查VideoPlayer组件的调用逻辑,确认没有直接给VideoPlayer组件加ref属性,所有ref都是通过普通prop传递,或者直接在组件内部声明ref即可解决function components cannot be given refs报错。

3. (可选)针对Next.js 13+ App Router的路由缓存优化

如果使用的是App Router,可以给CallPage页面添加dynamic = 'force-dynamic'配置,禁用页面路由缓存,确保每次进入页面都重新初始化组件相关逻辑:

// app/call/page.tsx
export const dynamic = 'force-dynamic'

内容的提问来源于stack exchange,提问作者Nicolás Guasca Santamaría

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 11:15:03