reactjs 具有www.example.com状态的UseEffect挂接socket.io在套接字处理程序中不是持久的

h22fl7wq  于 2023-01-30  发布在  React
关注(0)|答案(2)|浏览(140)

我有如下的React组分

function ConferencingRoom() {
    const [participants, setParticipants] = useState({})
    console.log('Participants -> ', participants)

    useEffect(() => {
        // messages handlers
        socket.on('message', message => {
            console.log('Message received: ' + message.event)
            switch (message.event) {
                case 'newParticipantArrived':
                    receiveVideo(message.userid, message.username)
                    break
                case 'existingParticipants':
                    onExistingParticipants(
                        message.userid,
                        message.existingUsers
                    )
                    break
                case 'receiveVideoAnswer':
                    onReceiveVideoAnswer(message.senderid, message.sdpAnswer)
                    break
                case 'candidate':
                    addIceCandidate(message.userid, message.candidate)
                    break
                default:
                    break
            }
        })
        return () => {}
    }, [participants])

    // Socket Connetction handlers functions

    const onExistingParticipants = (userid, existingUsers) => {
        console.log('onExistingParticipants Called!!!!!')

        //Add local User
        const user = {
            id: userid,
            username: userName,
            published: true,
            rtcPeer: null
        }

        setParticipants(prevParticpants => ({
            ...prevParticpants,
            [user.id]: user
        }))

        existingUsers.forEach(function(element) {
            receiveVideo(element.id, element.name)
        })
    }

    const onReceiveVideoAnswer = (senderid, sdpAnswer) => {
        console.log('participants in Receive answer -> ', participants)
        console.log('***************')

        // participants[senderid].rtcPeer.processAnswer(sdpAnswer)
    }

    const addIceCandidate = (userid, candidate) => {
        console.log('participants in Receive canditate -> ', participants)
        console.log('***************')
        // participants[userid].rtcPeer.addIceCandidate(candidate)
    }

    const receiveVideo = (userid, username) => {
        console.log('Received Video Called!!!!')
        //Add remote User
        const user = {
            id: userid,
            username: username,
            published: false,
            rtcPeer: null
        }

        setParticipants(prevParticpants => ({
            ...prevParticpants,
            [user.id]: user
        }))
    }

    //Callback for setting rtcPeer after creating it in child component
    const setRtcPeerForUser = (userid, rtcPeer) => {
        setParticipants(prevParticpants => ({
            ...prevParticpants,
            [userid]: { ...prevParticpants[userid], rtcPeer: rtcPeer }
        }))
    }

    return (
            <div id="meetingRoom">
                {Object.values(participants).map(participant => (
                    <Participant
                        key={participant.id}
                        participant={participant}
                        roomName={roomName}
                        setRtcPeerForUser={setRtcPeerForUser}
                        sendMessage={sendMessage}
                    />
                ))}
            </div>
    )
}

它所具有的唯一状态是调用内部的参与者的哈希表,使用useState钩子来定义它。
然后我使用useEffect监听聊天室的套接字事件,只有4个事件
然后,根据这些事件在服务器上的执行顺序,为这些事件定义4个回调处理程序
最后,我还有另一个回调函数,它被传递给列表中的每个子参与者,以便在子组件创建其rtcPeer对象之后,将其发送给父组件,以便在参与者的hashTable中的参与者对象上设置该对象
流程如下:参与者加入会议室-〉调用existingParticipants事件-〉创建本地参与者并将其添加到参与者hashTable,然后-〉recieveVideoAnswer,服务器多次发出candidate,如您在屏幕截图中所见
第一个事件状态是空的,接下来的两个事件是空的,然后又是空的,这个模式不断重复一个空的状态,然后接下来的两个是正确的,我不知道这个状态是怎么回事

lnlaulya

lnlaulya1#

这方面的困难之处在于,您遇到了几个相互影响的问题,这些问题会使您的故障排除感到困惑。
最大的问题是你设置了多个套接字事件处理器,每次重新呈现时,你都在调用socket.on,而从来没有调用过socket.off
对于如何处理这个问题,我可以设想三种主要的方法:

  • 设置一个套接字事件处理程序,并且只对participants状态使用函数更新。使用这种方法,您将对useEffect使用一个空的依赖项数组。并且您不会在效果中的任何地方**引用participants(包括消息处理程序调用的所有方法)。如果你引用participants,那么在第一次重新调用participants时,你将引用它的旧版本。如果需要对participants进行的更改可以使用函数更新轻松完成,那么这可能是最简单的方法。
  • 每次对participants进行更改时,请设置一个新的套接字事件处理程序。为了使其正常工作,您需要删除以前的事件处理程序否则您将拥有与呈现器相同数量的事件处理程序。如果您有多个事件处理程序,则创建的第一个事件处理程序将始终使用participants的第一个版本(空),第二个将始终使用participants的第二个版本,等等。这将工作,并提供更大的灵活性,在如何使用现有的participants状态,但有一个缺点,反复拆除和设置套接字事件处理程序,这感觉笨重。
  • 设置单个套接字事件处理程序并使用ref来访问当前participants状态。这与第一种方法类似,但添加了一个附加效果,该效果在每次呈现时执行,以将当前participants状态设置为ref,以便消息处理程序可以可靠地访问它。

无论您使用哪种方法,我认为如果您将消息处理程序移出呈现函数并显式传入其依赖项,您将更容易推理代码正在做什么。
第三个选项提供了与第二个选项相同的灵活性,同时避免了套接字事件处理程序的重复设置,但增加了管理participantsRef的复杂性。
下面是使用第三个选项时的代码(我还没有尝试执行它,所以我不能保证我没有小的语法问题):

const messageHandler = (message, participants, setParticipants) => {
  console.log('Message received: ' + message.event);

  const onExistingParticipants = (userid, existingUsers) => {
    console.log('onExistingParticipants Called!!!!!');

    //Add local User
    const user = {
      id: userid,
      username: userName,
      published: true,
      rtcPeer: null
    };

    setParticipants({
      ...participants,
      [user.id]: user
    });

    existingUsers.forEach(function (element) {
      receiveVideo(element.id, element.name)
    })
  };

  const onReceiveVideoAnswer = (senderid, sdpAnswer) => {
    console.log('participants in Receive answer -> ', participants);
    console.log('***************')

    // participants[senderid].rtcPeer.processAnswer(sdpAnswer)
  };

  const addIceCandidate = (userid, candidate) => {
    console.log('participants in Receive canditate -> ', participants);
    console.log('***************');
    // participants[userid].rtcPeer.addIceCandidate(candidate)
  };

  const receiveVideo = (userid, username) => {
    console.log('Received Video Called!!!!');
    //Add remote User
    const user = {
      id: userid,
      username: username,
      published: false,
      rtcPeer: null
    };

    setParticipants({
      ...participants,
      [user.id]: user
    });
  };

  //Callback for setting rtcPeer after creating it in child component
  const setRtcPeerForUser = (userid, rtcPeer) => {
    setParticipants({
      ...participants,
      [userid]: {...participants[userid], rtcPeer: rtcPeer}
    });
  };

  switch (message.event) {
    case 'newParticipantArrived':
      receiveVideo(message.userid, message.username);
      break;
    case 'existingParticipants':
      onExistingParticipants(
          message.userid,
          message.existingUsers
      );
      break;
    case 'receiveVideoAnswer':
      onReceiveVideoAnswer(message.senderid, message.sdpAnswer);
      break;
    case 'candidate':
      addIceCandidate(message.userid, message.candidate);
      break;
    default:
      break;
  }
};

function ConferencingRoom() {
  const [participants, setParticipants] = React.useState({});
  console.log('Participants -> ', participants);
    const participantsRef = React.useRef(participants);
    React.useEffect(() => {
        // This effect executes on every render (no dependency array specified).
        // Any change to the "participants" state will trigger a re-render
        // which will then cause this effect to capture the current "participants"
        // value in "participantsRef.current".
        participantsRef.current = participants;
    });

  React.useEffect(() => {
    // This effect only executes on the initial render so that we aren't setting
    // up the socket repeatedly. This means it can't reliably refer to "participants"
    // because once "setParticipants" is called this would be looking at a stale
    // "participants" reference (it would forever see the initial value of the
    // "participants" state since it isn't in the dependency array).
    // "participantsRef", on the other hand, will be stable across re-renders and 
    // "participantsRef.current" successfully provides the up-to-date value of 
    // "participants" (due to the other effect updating the ref).
    const handler = (message) => {messageHandler(message, participantsRef.current, setParticipants)};
    socket.on('message', handler);
    return () => {
      socket.off('message', handler);
    }
  }, []);

  return (
      <div id="meetingRoom">
        {Object.values(participants).map(participant => (
            <Participant
                key={participant.id}
                participant={participant}
                roomName={roomName}
                setRtcPeerForUser={setRtcPeerForUser}
                sendMessage={sendMessage}
            />
        ))}
      </div>
  );
}

另外,下面是一个模拟上述代码中发生的情况的工作示例,但未使用socket,以便清楚地显示使用participantsparticipantsRef之间的区别。观察控制台并单击两个按钮,以查看将participants传递到消息处理程序的两种方法之间的区别。

import React from "react";

const messageHandler = (participantsFromRef, staleParticipants) => {
  console.log(
    "participantsFromRef",
    participantsFromRef,
    "staleParticipants",
    staleParticipants
  );
};

export default function ConferencingRoom() {
  const [participants, setParticipants] = React.useState(1);
  const participantsRef = React.useRef(participants);
  const handlerRef = React.useRef();
  React.useEffect(() => {
    participantsRef.current = participants;
  });

  React.useEffect(() => {
    handlerRef.current = message => {
      // eslint will complain about "participants" since it isn't in the
      // dependency array.
      messageHandler(participantsRef.current, participants);
    };
  }, []);

  return (
    <div id="meetingRoom">
      Participants: {participants}
      <br />
      <button onClick={() => setParticipants(prev => prev + 1)}>
        Change Participants
      </button>
      <button onClick={() => handlerRef.current()}>Send message</button>
    </div>
  );
}

emeijp43

emeijp432#

创建一个单例socket对象,并在闭包的帮助下在任何其他处理程序中使用该示例。

// socketHandleInitializer.js

let socket=null;

// when we call this function on top level our app, socket will be initialized
export const socketConnection=()=>{
    socket = io("http://localhost:5000", {optionsObj});
    // you can write listeners here
    socket.on("connect", () => {
        console.log("succesfully connected with scoket io server");
   }
}
// you can write functions to emit event to the server here
// Because you have access to socket obj
// you can use those functions alone anywhere in your app
export const joinRoom = (data) => {
  socket.emit("room-join", data);
};

相关问题