React SocketContext: 2个组件使用1个Websocket

huangapple go评论63阅读模式
英文:

React SocketContext: 2 Components using 1 Websocket

问题

我的问题

我有一个服务器,通过UDP接收流数据并通过Websockets转发数据。在我的前端中,有两个不同的组件来接收数据并渲染数值。每当我的服务器广播对象时,组件1会捕获键A,而键B应该由组件B捕获。为了调试目的,如果我只使用一个组件,下面的代码可以正常工作,但一旦我同时使用两个组件,只有一个能够正确地获取和更新数据,而另一个什么都不做。我做错了什么?

我的服务器(仅关键部分)

# 用于保存值的字典
var data = {"score": 0, "session": 0}

# 处理不同的UDP数据包
client.on(PACKETS.session, handle_session);
client.on(PACKETS.score, handle_score);

# 使用新值更新data.session对象
function handle_session(data) {
    data['session'] = data.session
    websocket.broadcast(data);
}

# 使用新值更新data.score对象
function handle_score(data) {
    data['score'] = data.score
    websocket.broadcast(data);
}

# 在处理函数中调用广播数据函数
websocket.broadcast = function broadcast(data) {
  websocket.clients.forEach(function each(client) {
    console.log(data);
    client.send(data);
  });
};

我的React应用

CONTEXT.JS - 创建我的socket

export const socket = new WebSocket("ws://localhost:8000")
export const SocketContext = React.createContext();

APP.JS - 我在SocketContext中包装了我的两个组件

function App() {
  return (
    <div className="App">
      <SocketContext.Provider value={socket}>
        <Score />
        <Session />
      </SocketContext.Provider>
    </div>
  );
}

SCORE.JS - 组件1从上下文中加载socket

function Score() {

  const [score, setScore] = useState(0)
  const websocket = useContext(SocketContext);
   
  useEffect(() => { 
    websocket.onmessage = function(e) {
      var packet = JSON.parse(e.data);
      setScore(packet.score)
    }
  }, [websocket,]);

  return (
    <h6>{score}</h6> 
  );
}

export default Score;

SESSION.JS - 组件2从上下文中加载相同的socket

function Session() {

  const [session, setSession] = useState(0)
  const websocket = useContext(SocketContext);
   
  useEffect(() => { 
    websocket.onmessage = function(e) {
      var packet = JSON.parse(e.data);
      setSession(packet.session)
    }
  }, [websocket,]);

  return (
    <h6>{session}</h6> 
  );
}

export default Session;

在运行前端并在两个组件的useEffect中添加控制台日志时,只有一个会同时运行。然后重新加载应用程序时,另一个会运行。它们永远不会同时运行。

英文:

MY PROBLEM

I have a server which receives streaming data over UDP and rebroadcasts the data over websockets. In my front-end I have 2 different components which pick up the data and render the values. Whenever my server broadcast the object, key A is picked up by component 1. And key B should be picked up by component B.
For debugging purposes, the code below works smooth if I were to use a single component, but as soon as I use both only 1 is able to fetch and update data correctly while the other does nothing. What am I doing wrong?

MY SERVER (Just the important stuff)

# Dict to hold values
var data = {&quot;score&quot; : 0, &quot;session&quot; : 0,}

# handle different incoming UDP packets
client.on(PACKETS.session, handle_session);
client.on(PACKETS.score, handle_score);

#update data.session object with new values
function handle_session(data) {
    data[&#39;session&#39;] = data.session
    websocket.broadcast(data);
}

#update data.score object with new values
function handle_score(data) {
    data[&#39;score&#39;] = data.score
    websocket.broadcast(data);
}

# Broadcast data function gets called in handle functions.
websocket.broadcast = function broadcast(data) {
  websocket.clients.forEach(function each(client) {
    console.log(data);
    client.send(data);
  });
};

MY REACT APP

CONTEXT.JS - Create my socket

export const socket = new WebSocket(&quot;ws://localhost:8000&quot;)
export const SocketContext = React.createContext();

APP.JS - I wrap both my components in SocketContext

function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;SocketContext.Provider value={socket}&gt;
        &lt; Score /&gt;
        &lt; Session /&gt;
      &lt;/SocketContext.Provider&gt;
    &lt;/div&gt;

SCORE.JS - Component number 1 loads the socket from context.

function Score() {

  const [score, setScore] = useState(0)
  const websocket = useContext(SocketContext);
   
  useEffect(() =&gt; { 
    websocket.onmessage = function(e) {
      var packet = JSON.parse(e.data);
      setScore(packet.score)
    }
  }, [websocket,]);

  return (
    &lt;h6&gt;{score}&lt;/&gt; 
  );
}

export default Score;

SESSION.JS - Component number 2 loads the same socket from context.

function Session() {

  const [session, setSession] = useState(0)
  const websocket = useContext(SocketContext);
   
  useEffect(() =&gt; { 
    websocket.onmessage = function(e) {
      var packet = JSON.parse(e.data);
      setSession(packet.session)
    }
  }, [websocket,]);

  return (
    &lt;h6&gt;{session}&lt;/&gt; 
  );
}

export default Session;

So when running the frontend and adding console logging in useEffect for both components, Only one runs at the time. Then when reloading the app. Another one runs. They never run both at the same time.
React SocketContext: 2个组件使用1个Websocket

答案1

得分: 1

以下是翻译好的部分:

"你在控制台中是否有任何具体的错误消息?"

"服务器端的这部分似乎有问题:"

data['score'] = {data.score}

看起来应该是:

data['score'] = data.score;

"还有另一个问题是变量作用域的覆盖:"

你设置了:

var data = {"score": 0, "session": 0,}

但后来你的两个函数都在它们的本地作用域中使用了 data,这不是一个好的做法,会导致问题:

function handle_session(data) {

可能应该是:

function handle_session(d) {

还有这个:

websocket.broadcast = function broadcast(data) {

可能应该是:

websocket.broadcast = function broadcast(d) {

这些函数可以共享相同的内部作用域变量 d,因为它们是兄弟函数。

"编辑,更新的答案"

问题出在使用 websocket.onmessage 上。由于 websocket 是在组件之间共享的单个实例,一旦在 App.js 中的 onmessage 属性上设置了监听器,后续的子组件在尝试在相同的websocket实例的相同属性上设置相同的监听器时将被忽略。

关键在于使用 addListener
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/message_event

我创建了一个简单的可工作的 React <> Express <> Websocket 应用示例,解决了你的情况,使用了 addListener。我重现了你的确切问题。

现在所有三个组件的 useEffect 将按照你的预期正常监听。

打开终端并运行:npm run dev
https://codesandbox.io/p/sandbox/bitter-mountain-q3x118

希望有所帮助!

英文:

Do you have any specific error messages in the console?

This bit on the server-side seems problematic:

data[&#39;score&#39;] = {data.score}

It seems that should be:

data[&#39;score&#39;] = data.score;

Edit*

Also another issue I see is variable scope overwriting:

You set:

var data = {&quot;score&quot; : 0, &quot;session&quot; : 0,}

But later both your functions use data in their local scope which is not good and would cause issues:

function handle_session(data) {

Should probably be:

function handle_session(d) {

Also this:

websocket.broadcast = function broadcast(data) {

Should probably be:

websocket.broadcast = function broadcast(d) {

These functions can share the same inner scope variable, d because they are siblings.

***EDIT, UPDATED ANSWER

The issue is with the use of websocket.onmessage. Since websocket is a single instance shared between components, once the listener is set on the onmessage property inside App.js, subsequent children components will be ignored when they try to set the same listener on the same property on the same websocket instance.

The key is to use addListener instead!
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/message_event

I created a simple working React <> Express <> Websocket App example of your situation, resolved by using addListener. I reproduced your exact problem.

Now all 3 component's useEffects will properly listen as you expected.

Open a terminal and run: npm run dev
https://codesandbox.io/p/sandbox/bitter-mountain-q3x118

Hope that helps!

huangapple
  • 本文由 发表于 2023年2月7日 02:33:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365275.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定