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
相关产品推荐
相关产品推荐

