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

React重渲染行为与引用相等性问题排查

问题根源分析

你的useMemo看似没生效,核心原因有两个:

  1. 每次轮询都会触发messages引用更新
    每次Apollo轮询返回数据时,你执行setMessages((prev) => [...prev, ...newMessages]),这会生成一个全新的messages数组引用。而useMemo的依赖是[messages],只要messages引用变了,useMemo就会重新执行去重逻辑,返回一个新的数组引用——这就导致memoized和formerRef.current永远是不同的引用,控制台永远输出not the same。

  2. 去重逻辑的时机错误
    你在状态更新后再做去重,相当于先把重复消息加到messages里,再过滤一遍。这种做法不仅浪费计算资源,还会因为messages的频繁更新触发组件重渲染,完全违背了用useMemo优化的初衷。

另外你怀疑的对象引用问题确实存在:即使后端返回的消息内容完全一致,每次轮询得到的对象都是新的引用。如果你的消费者组件用了React.memo,默认浅比较会认为这些是不同的对象,依然会触发重渲染。


解决方案

方案1:在状态更新时就完成去重(最优)

把去重逻辑移到setMessages的更新函数里,只有当有真正的新消息时才更新状态,从源头避免不必要的重渲染:

const [messages, setMessages] = useState<Message[]>([]);
const formerRef = useRef<Message[]>([]);

useQuery(MESSAGES_QUERY, {
  variables: { gameId },
  pollInterval: 500,
  onCompleted: (data) => {
    setCursor(data.messagesByGame.cursor);
    setMessages((prev) => {
      // 用Set存储已有消息的id,O(1)查询效率
      const existingIds = new Set(prev.map(m => m.id));
      // 过滤出真正的新消息
      const newUniqueMessages = data.messagesByGame.messages.filter(
        m => !existingIds.has(m.id)
      );
      
      // 没有新消息就返回原数组,不触发状态更新
      if (newUniqueMessages.length === 0) return prev;
      
      // 合并新消息,返回新数组(只有此时才会触发重渲染)
      return [...prev, ...newUniqueMessages];
    });
  },
});

// 现在只有当有新消息时,messages才会更新,useMemo也只会在此时执行
const memoized = useMemo(() => messages, [messages]);

// 此时控制台只会在有新消息时输出not the same
if (memoized !== formerRef.current) {
  console.log('not the same', memoized);
} else {
  console.log('is the same', memoized);
}
formerRef.current = memoized;

方案2:解决对象引用导致的消费者重渲染

如果你的消费者组件用了React.memo但依然重渲染,是因为新消息的对象引用是全新的。可以在状态更新时,对重复消息保留原引用:

setMessages((prev) => {
  // 用Map存储已有消息的id和对应对象引用
  const existingMessageMap = new Map(prev.map(m => [m.id, m]));
  const newMessages = data.messagesByGame.messages;
  
  let hasNewMessages = false;
  // 遍历新消息,保留原引用或添加新消息
  newMessages.forEach(m => {
    if (!existingMessageMap.has(m.id)) {
      existingMessageMap.set(m.id, m);
      hasNewMessages = true;
    }
  });
  
  // 没有新消息就返回原数组
  if (!hasNewMessages) return prev;
  
  // 转换为数组返回(此时重复消息用的是原引用)
  return Array.from(existingMessageMap.values());
});

这样消费者组件的React.memo浅比较就能识别出重复消息的引用未变,不会触发不必要的重渲染。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 03:53:16