Pubnub分属不同React组件层级时重复接收消息问题求助
问题描述
页面刷新后,Pubnub_A组件仅接收1条消息,但Pubnub_B组件会重复接收消息,场景细节如下:
- 初始代码中
Pubnub_A是<Suspense>的直接子节点,Pubnub_B放在<Suspense>嵌套的Content组件内 - 若将两个组件放在同一层级(同属
<Suspense>直接子节点或都在Content组件内),两者均仅接收1条消息 - 移除
Pubnub_A后,Pubnub_B也仅接收1条消息
相关代码
业务代码
const NodeDetails = (): React.Node => { ...... return ( <Suspense fallback={<Loader loaded={false} />}> <Pubnub_A /> <Content ...... /> </Suspense> ); }; function Content(): React.Node { ...... return ( <> <Pubnub_B /> ...... </> ); }
Pubnub_A组件
export default function Pubnub_A(): null { pubNubSub({ namespace: "test_A", namespaceId: "test_namespaceId", event: "DONE", callback: () => { console.log('Pubnub_A') }, }); return null; }
Pubnub_B组件
export default function Pubnub_B(): null { pubNubSub({ namespace: "test_B", namespaceId: "test_namespaceId", event: "DONE", callback: () => { console.log('Pubnub_B') }, }); return null; }
pubNubSub工具函数
export type Sub = { namespace: string, namespaceId?: ?string, event?: ?string, callback: EventCallback, }; export default function pubNubSub( sub: Sub ) { const subs = useMemo(() => [sub], [sub]); const {client} = useContext(PubNubContext); useEffect(() => { const ids = subs.map(sub => { return client.subscribe( { namespace: sub.namespace, namespaceId: sub.namespaceId, event: sub.event, }, sub.callback ) } ); return function clear() { ids.forEach(id => { client.unsubscribe(id); }); }; }, [subs, client]); }
问题原因
核心问题源于<Suspense>的渲染机制和回调引用不稳定:
- React的
<Suspense>会分两阶段渲染:先展示fallback,等待内部异步逻辑完成后,重新渲染整个包裹的内容。 Pubnub_A作为直接子节点,第一阶段就完成渲染,第二阶段React会复用其组件实例,仅执行一次订阅逻辑。Pubnub_B所在的Content组件在第二阶段才完成完整渲染,且每次渲染时都会创建新的匿名回调函数,导致pubNubSub中sub对象的引用变化,触发useEffect重新执行。- 旧订阅的清理逻辑可能存在延迟,新订阅已绑定,同一条消息会被多个回调实例接收,造成重复。
解决方案
针对初始代码结构,通过以下两步修复:
1. 稳定回调函数的引用
在Pubnub_A和Pubnub_B组件中,用useCallback包裹回调,避免每次渲染生成新的引用:
// Pubnub_A优化后 export default function Pubnub_A(): null { const handleCallback = useCallback(() => { console.log('Pubnub_A') }, []); pubNubSub({ namespace: "test_A", namespaceId: "test_namespaceId", event: "DONE", callback: handleCallback, }); return null; } // Pubnub_B优化后 export default function Pubnub_B(): null { const handleCallback = useCallback(() => { console.log('Pubnub_B') }, []); pubNubSub({ namespace: "test_B", namespaceId: "test_namespaceId", event: "DONE", callback: handleCallback, }); return null; }
2. 优化pubNubSub的依赖逻辑
移除冗余的useMemo,直接监听订阅的关键字段,避免依赖整个sub对象的引用:
export default function pubNubSub(sub: Sub) { const { client } = useContext(PubNubContext); useEffect(() => { const id = client.subscribe( { namespace: sub.namespace, namespaceId: sub.namespaceId, event: sub.event, }, sub.callback ); return () => { client.unsubscribe(id); }; }, [client, sub.namespace, sub.namespaceId, sub.event, sub.callback]); }
内容的提问来源于stack exchange,提问作者Hayden
相关产品推荐
相关产品推荐

