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

PubNub removeListener在useEffect返回时未触发导致重入聊天消息重复

问题根因

  • 当前useEffect依赖数组为空,闭包捕获的listener实例引用在组件卸载时可能出现匹配失效,导致removeListener无法定位到要移除的监听器,造成监听器残留。
  • 每次进入聊天页面都执行pubnub.setUUID(employer._id)会重置PubNub连接状态,旧监听器会和原有连接实例解绑,移除操作无法作用到残留的监听器。
  • 清理逻辑顺序有误:先移除监听器再取消订阅,取消订阅过程中触发的消息回调依然会被未完成移除的监听器捕获。

修复方案

  1. 将useEffect中用到的所有外部变量全部加入依赖数组,保证闭包拿到的始终是最新的实例引用。
  2. 将pubnub.setUUID逻辑移到应用初始化阶段执行,不要每次进入聊天页面重复设置。
  3. 调整清理逻辑顺序,先取消订阅再移除监听器。

修复后代码参考:

useEffect(() => {
  const listener = {
    message: (envelope: any) => {
      if (envelope) {
        const message = {
          channel: envelope.channel,
          message: {
            ...envelope.message,
          },
          uuid: envelope.publisher,
          timetoken: envelope.timetoken,
        }
        dispatch(setMessage(message))
        console.log('Message listener activated!') 
      }
    },
  }

  pubnub.addListener(listener)

  pubnub.fetchMessages(
    {
      channels: [ch],
      count: 100,
    },
    (status, response) => {
      if (response.channels[ch]) {
        dispatch(setMessages(response?.channels[ch]))
      } else {
        dispatch(setMessages([]))
      }
    },
  )
  pubnub.subscribe({ channels: [ch] })

  const usersInfo = channel.split('_')
  if (channel != employer._id && usersInfo[1] !== 'job') {
    const deeberId = usersInfo[0]
    getCandidateById(deeberId).then(res => {
      dispatch(setSelectedChatCandidate(res))
    })
  }
  renderDisplayName()

  return () => {
    // 先取消订阅停止接收新消息
    pubnub.unsubscribeAll()
    // 再移除监听器
    pubnub.removeListener(listener) 
  }
// 所有用到的外部变量全部加入依赖数组
}, [pubnub, employer._id, ch, channel, dispatch, getCandidateById, renderDisplayName])

额外优化:如果要彻底避免重复消息,可以在存储消息时根据timetoken去重,同一个timetoken的消息只保留一份。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 15:06:00