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

React中useEffect/useCallback高频信号下未触发的问题求助

解决方案:绕开React状态批量更新,直接在SignalR回调中处理ICE候选

你的问题核心在于React的状态更新是异步批量处理的,高频调用setCurrentSignal时,React会合并多次更新,导致useEffect/useCallback只捕获到最终的几次状态变化,漏掉了中间的ICE候选。

直接参考你熟悉的.NET组合模式思路,在React里可以这样实现:

1. 直接在SignalR回调中调用PeerConnection方法

不要通过React状态中转高频的ICE消息,而是让SignalR的message监听器直接调用RTCPeerConnection的相关方法,绕开状态更新的合并机制。

修改signalRContext.tsx

让Context接收外部传入的PeerConnection实例,在SignalR的消息回调中直接处理ICE候选:

import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';

interface SignalRContextType {
  connection: HubConnection | null;
}

const SignalRContext = createContext<SignalRContextType>({ connection: null });

export const SignalRContextProvider = ({ 
  children, 
  peerConnection 
}: { 
  children: React.ReactNode; 
  peerConnection: RTCPeerConnection | null 
}) => {
  const [connection, setConnection] = useState<HubConnection | null>(null);

  useEffect(() => {
    const initConnection = async () => {
      const conn = new HubConnectionBuilder()
        .withUrl('/your-signalr-hub-url')
        .build();

      // 直接在SignalR回调中处理ICE候选
      conn.on('message', (payload) => {
        console.log('MESSAGE', payload);
        if (peerConnection && payload.type === 'ice-candidate') {
          // 直接调用PeerConnection的addIceCandidate方法
          peerConnection.addIceCandidate(new RTCIceCandidate(payload.candidate))
            .catch(err => console.error('添加ICE候选失败:', err));
        }
        // 其他类型消息(如offer/answer)仍可通过状态中转(如果不需要高频处理)
      });

      await conn.start();
      setConnection(conn);
    };

    if (peerConnection) {
      initConnection();
    }

    return () => {
      connection?.stop();
    };
  }, [peerConnection]);

  return (
    <SignalRContext.Provider value={{ connection }}>
      {children}
    </SignalRContext.Provider>
  );
};

export const useSignalRContext = () => useContext(SignalRContext);

修改useRTCPeerConnection.ts

创建PeerConnection实例,并传递给SignalR Context:

import { useState, useEffect } from 'react';
import { useSignalRContext } from './signalRContext';

export const useRTCPeerConnection = () => {
  const [peerConnection, setPeerConnection] = useState<RTCPeerConnection | null>(null);
  const { connection } = useSignalRContext();

  useEffect(() => {
    const pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
    });

    // 本地ICE候选发送逻辑
    pc.onicecandidate = (event) => {
      if (event.candidate && connection) {
        connection.invoke('SendMessage', {
          type: 'ice-candidate',
          candidate: event.candidate
        }).catch(err => console.error('发送ICE候选失败:', err));
      }
    };

    // 其他WebRTC逻辑(如offer/answer处理)
    pc.onnegotiationneeded = async () => {
      // ... 你的协商逻辑
    };

    setPeerConnection(pc);

    return () => {
      pc.close();
    };
  }, [connection]);

  return { peerConnection };
};

父组件组合使用

import { useRTCPeerConnection } from './useRTCPeerConnection';
import { SignalRContextProvider } from './signalRContext';

function App() {
  const { peerConnection } = useRTCPeerConnection();

  return (
    <SignalRContextProvider peerConnection={peerConnection}>
      {/* 你的视频通话组件 */}
    </SignalRContextProvider>
  );
}

export default App;

2. 为什么原方案失效?

React在处理高频setState时,会自动合并多次状态更新为一次,目的是提升性能。这就导致你看到20条MESSAGE日志,但useEffect只触发4-7次——因为中间的状态更新被合并了,React只保留了最终的状态值。

3. 可选优化:过滤重复ICE候选

如果仍有冗余候选,可以在发送或接收端过滤重复的候选(根据candidate字段的内容判断),减少处理量,但这不是必须的,WebRTC本身也会忽略无效候选。

内容的提问来源于stack exchange,提问作者VladL

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 10:40:25