WebRTC+Socket.io视频通话:React状态Local Stream在Socket事件后为null
问题分析与解决方案
核心问题
你遇到的问题是闭包导致的旧状态捕获:
handleCallAccepted通过useCallback创建,依赖项为空数组,因此它捕获的是组件初始渲染时的state(此时stream为null)。- 后续
turnOnYourVideo成功获取媒体流并更新state后,handleCallAccepted并未重新创建,所以当socket触发video:call:accepted事件时,回调内访问的仍是初始的null值。
解决方案
方法1:更新useCallback的依赖数组
将state加入useCallback的依赖列表,确保回调始终能访问最新的状态。同时更新监听socket的useEffect依赖,保证回调更新时重新绑定事件:
const handleCallAccepted = useCallback(({ from, ans }) => { console.log('from', from); console.log('ans', ans); console.log('handleCallAccepted', state?.stream); // 获取LocalStream的tracks if (state?.stream) { const tracks = state.stream.getTracks(); console.log('Local Stream Tracks', tracks); } }, [state]); // 添加state到依赖 useEffect(() => { socket.on('video:call:accepted', handleCallAccepted); return () => { socket.off('video:call:accepted', handleCallAccepted); } }, [handleCallAccepted]); // 添加handleCallAccepted到依赖
方法2:使用useRef保存媒体流引用
如果不想频繁创建回调,可以用useRef存储媒体流的可变引用,避免闭包问题:
const WebRTC = () => { const [state, dispatch] = useReducer(reducer, initialState); const streamRef = useRef(null); // 创建ref存储媒体流 const turnOnYourVideo = async () => { const constraints = { audio: true, video: true } const localStream = await navigator.mediaDevices.getUserMedia(constraints); dispatch({ type: 'stream', stream: localStream }); streamRef.current = localStream; // 更新ref的current值 } const handleCallAccepted = useCallback(({ from, ans }) => { console.log('from', from); console.log('ans', ans); console.log('handleCallAccepted', streamRef.current); // 获取LocalStream的tracks if (streamRef.current) { const tracks = streamRef.current.getTracks(); console.log('Local Stream Tracks', tracks); } }, []); // 依赖为空,ref的current是可变值不受闭包影响 // 其余代码不变... };
额外注意
你代码中存在事件名拼写错误:video:call:accepteed应该修正为video:call:accepted,需要统一修正两端的事件名,避免事件无法正常触发。
内容的提问来源于stack exchange,提问作者Vikram Singh
相关产品推荐
相关产品推荐

