React项目使用PubNub SDK开发聊天组件出现消息重复/无消息问题
问题根因
- PubNub监听器无法正常移除:
pubnub.removeListener要求传入和addListener完全一致的回调引用,当前代码在清理函数中重新定义了匿名回调,和注册时的回调不是同一个引用,导致旧监听器永远残留。每次切换频道都会新增一个监听器,同一条消息会被多次捕获执行消息插入逻辑,直接导致消息重复渲染。 - 发送消息逻辑存在闭包陷阱:负责发送消息的useEffect依赖项配置错误,仅声明了
sendMessage作为依赖,但sendMessage是每次组件渲染都会重新生成的新函数,会导致effect无意义重复触发;同时effect内部用到的message、chatRoom没有加入依赖项,会读取到旧的状态值,导致消息发送失败、不展示。
修复代码
import { useState, useEffect, useCallback } from 'react'; export default function Chat({ chatRoom, person, task }) { const [messages, setMessages] = useState([]); const [message, setMessage] = useState(""); // 用useCallback包裹监听器回调,保证引用稳定 const handlePubnubMessage = useCallback((message) => { if (message) { setMessages((prev) => [...prev, message]); console.log(message.message); } scrollingComponent.current?.scrollIntoView({ behavior: "smooth" }); }, []); const handlePubnubFile = useCallback((file) => { // 把你原有handleFile的逻辑移到此处 }, []); useEffect(() => { console.log("ADDING LISTENER"); const listener = { message: handlePubnubMessage, file: handlePubnubFile }; pubnub.addListener(listener); pubnub.subscribe({ channels: [chatRoom] }); // 拉取当前频道历史消息 pubnub.history({ channel: chatRoom, count: 50 // 可根据需求调整拉取的历史消息数量 }).then((res) => { setMessages(res.messages.map(item => item.entry)); }); // 清理函数直接传同一个listener对象即可正常移除监听器 return () => { pubnub.removeListener(listener); pubnub.unsubscribe({ channels: [chatRoom] }); setMessages([]); }; }, [chatRoom, handlePubnubMessage, handlePubnubFile]); // 直接把发送逻辑写到sendMessage里,避免多余useEffect带来的问题 const sendMessage = async () => { if (!message.trim()) return; try { await pubnub.publish({ channel: chatRoom, message: message, storeInHistory: true, }); await Promise.all([ pubnub.signal({ channel: chatRoom, message: "notification" }), pubnub.signal({ channel: chatRoom, message: "not_typing" }) ]); setMessage(""); } catch (error) { setMessages((prev) => [...prev, error.toString()]); } }; // 输入框提交事件直接绑定sendMessage即可,不需要额外监听message状态的useEffect // code continues... }
额外注意点
- 滚动操作前加可选链
?.判断scrollingComponent是否存在,避免组件卸载时执行逻辑报错。 - 取消订阅时指定对应频道,不要用
unsubscribeAll,避免影响其他组件的PubNub订阅逻辑。
内容的提问来源于stack exchange,提问作者Vasil Kostadinov
相关产品推荐
相关产品推荐

