React重渲染行为与引用相等性问题排查
问题根源分析
你的useMemo看似没生效,核心原因有两个:
每次轮询都会触发
messages引用更新
每次Apollo轮询返回数据时,你执行setMessages((prev) => [...prev, ...newMessages]),这会生成一个全新的messages数组引用。而useMemo的依赖是[messages],只要messages引用变了,useMemo就会重新执行去重逻辑,返回一个新的数组引用——这就导致memoized和formerRef.current永远是不同的引用,控制台永远输出not the same。去重逻辑的时机错误
你在状态更新后再做去重,相当于先把重复消息加到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
相关产品推荐
相关产品推荐

