PubNub removeListener在useEffect返回时未触发导致重入聊天消息重复
问题根因
- 当前
useEffect依赖数组为空,闭包捕获的listener实例引用在组件卸载时可能出现匹配失效,导致removeListener无法定位到要移除的监听器,造成监听器残留。 - 每次进入聊天页面都执行
pubnub.setUUID(employer._id)会重置PubNub连接状态,旧监听器会和原有连接实例解绑,移除操作无法作用到残留的监听器。 - 清理逻辑顺序有误:先移除监听器再取消订阅,取消订阅过程中触发的消息回调依然会被未完成移除的监听器捕获。
修复方案
- 将
useEffect中用到的所有外部变量全部加入依赖数组,保证闭包拿到的始终是最新的实例引用。 - 将
pubnub.setUUID逻辑移到应用初始化阶段执行,不要每次进入聊天页面重复设置。 - 调整清理逻辑顺序,先取消订阅再移除监听器。
修复后代码参考:
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
相关产品推荐
相关产品推荐

