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

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>的渲染机制和回调引用不稳定:

  1. React的<Suspense>会分两阶段渲染:先展示fallback,等待内部异步逻辑完成后,重新渲染整个包裹的内容。
  2. Pubnub_A作为直接子节点,第一阶段就完成渲染,第二阶段React会复用其组件实例,仅执行一次订阅逻辑。
  3. Pubnub_B所在的Content组件在第二阶段才完成完整渲染,且每次渲染时都会创建新的匿名回调函数,导致pubNubSub中sub对象的引用变化,触发useEffect重新执行。
  4. 旧订阅的清理逻辑可能存在延迟,新订阅已绑定,同一条消息会被多个回调实例接收,造成重复。

解决方案

针对初始代码结构,通过以下两步修复:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:50:24