【问题标题】:Server's socket.io listener fires twice for no apparent reason服务器的 socket.io 监听器无故触发两次
【发布时间】:2021-10-29 09:44:59
【问题描述】:

我正在使用 express.js、react.js 和 socket.io 构建一个具有聊天功能的应用程序。 Here is a minimal version with the bug reproduced on Github.

问题是服务器的socket.on() 总是触发两次,这对我来说没有什么明显的原因。

据我所知,客户端只发送一个.emit,但服务器的.on 总是触发两次(我相当确定事件处理程序不会绑定两次),无论有多少客户端已连接,或者实际上我是如何重构代码的。

发送客户端的表单(输入 + 按钮)将消息附加到其本地消息列表中,然后将 MESSAGE_NEW 发送到减速器。

首先是Socket.js 文件。

import React from 'react'
import socketio from 'socket.io-client'

export const socket = socketio.connect('ws://localhost:3001')
export const SocketContext = React.createContext()

这是来自Chat.js 的提交处理程序代码(这似乎工作正常,只调度一次):

const handleSubmit = e => {
  e.preventDefault()

  if (message === '') return

  setMessages(messages => [...messages, message])
  dispatch({ type: 'MESSAGE_NEW', payload: message })
  setMessage('')
}

这是/Reducer.js.emit 是信号(似乎也可以正常工作,只触发一次)。

import { useContext } from 'react'
import { SocketContext } from './Socket'

export default function Reducer(state, action) {
  const socket = useContext(SocketContext)

  switch (action.type) {
    case 'MESSAGE_NEW':
      // this only fires once, as it should
      console.log('emitting socket')
      socket.emit('message:new', action.payload)
      break

    default:
      return state
  }
}

但这里是来自/server/index.js 的相关部分,这就是问题所在。无论我尝试过什么,.on 总是会触发两次。

io.on('connection', socket => {
  console.log('New client connected:', socket.id)

  socket.on('message:new', message => {
    // this always fires twice, i do not understand why
    console.log('message:new: ', message, socket.id)
    socket.broadcast.emit('message:new', message)
  })
})

更新: 我发现将socket.emitReducer.js 移动到Chat.js 中的handleSubmit 可以正常工作。我不明白为什么,因为 Reducer 不会触发两次。

【问题讨论】:

    标签: reactjs express socket.io


    【解决方案1】:

    https://github.com/Heilemann/sockettest/blob/c1df22349ac1877ed625a01b14f48730b7af122d/client/src/Chat.jsx#L14-L22 中添加一个事件侦听器,然后尝试再次删除它。但是既然你写出了处理函数

    message => {
      setMessages([...messages, message])
    }
    

    每次删除都无效,因为要删除的处理程序与您添加的处理程序不同。您必须将处理函数分配给一个变量并在.on.off 中使用该变量。

    即使这不能解决问题,仍然值得这样做。

    【讨论】:

    • 我推送了一个可以做到这一点的更改。不幸的是,它不能解决问题。
    【解决方案2】:

    你的组件渲染了两次,调用了两次socket,给出了两条连接消息。

    像这样使用你的 useEffect 确保它渲染一次

    在你的 chat.js 中导入 socket.io

    import socketio from 'socket.io-client'
    let socket
    useEffect(() => {
        socket = socketio.connect('ws://localhost:3001')
        return () => {
          socket.off('message:new', listener)
        }
      },[])
    

    在您的提交处理程序中

      const handleSubmit = e => {
        e.preventDefault()
        if (message === '') return
        setMessages(messages => [...messages, message])
        socket.emit('message:new', message)
        //dispatch({ type: 'MESSAGE_NEW', payload: message })
        setMessage('')
      }
    

    传入一个空数组作为第二个参数意味着 useEffect 只会被调用一次。底线是确保 socket.io 客户端被调用一次。 props 和 state 的变化会导致组件重新渲染,从而导致 socket 被多次连接。您可以探索其他更清洁的模式。但是,这是您需要做的。

    【讨论】:

    • 我尝试创建 PR 让您检查更改,但权限被拒绝
    • 今天晚些时候我会试试这个,谢谢。但是,如果组件渲染两次,我认为分散在各处的所有 console.logs 也会触发两次,对吧?但他们没有?
    • 我想我解决了拉取请求问题。但是如果你把 socket 逻辑移到 Chat.js 中,Reducer 就无法访问它了?
    • 如果你将 console.log 放在你的 useEffect 中,你会看到它在你发送消息时运行了两次
    • 是的,Reducer 无法访问它
    【解决方案3】:

    根据React documentation's on strict mode

    严格模式无法自动为您检测副作用,但可以通过使它们更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:

    • 类组件constructorrendershouldComponentUpdate方法
    • 类组件静态getDerivedStateFromProps方法
    • 函数组件体
    • 状态更新函数(setState 的第一个参数)
    • 传递给useStateuseMemouseReducer 的函数

    注意最后一行:传递给useReducer 的函数将被调用两次;这就是为什么您的消息被发送两次的原因。您可以通过像这样更新减速器来确认这一点:

      case "MESSAGE_NEW":
          alert("I'm running");
    

    感谢React.StrictMode,您会注意到此警报出现了两次。

    如果您删除 src/App.js 中的 <React.StrictMode> 包装器,test 消息将不再重复;但是,它并不能解决这里的根本问题。

    根本问题是你让你的 reducer(应该是纯的)执行不纯的副作用(在这种情况下,通过 websocket 发送消息)。此外,reducer 函数还有一个 useContext 调用——从 React 的角度来看,这也是一个空操作,不是好的做法。

    正如您在问题中遇到的那样,只需将 socket.emit 移至您的 handleSubmit 即可。或者,如果你想走 reducer 路线,你可以考虑更复杂的设置;一些流行的配置包括 Redux + Redux Thunk 或 Redux + Redux Saga。您的里程可能会有所不同。

    【讨论】:

      【解决方案4】:

      我调试了您的代码,发现减速器触发两次的问题。

      解决办法是去掉<React.StrictMode> index.js 中的 App 标签周围的标签。

      更多信息StrictMode: https://reactjs.org/docs/strict-mode.html

      【讨论】:

        猜你喜欢
        • 2015-11-22
        • 2017-12-03
        • 1970-01-01
        • 2017-01-08
        • 1970-01-01
        • 1970-01-01
        • 2021-02-28
        • 1970-01-01
        • 2020-06-24
        相关资源
        最近更新 更多