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

Firestore查询结果异常:onSnapshot获取聊天数据返回错误结果

React + Firestore: onSnapshot 两次执行结果不一致,getDocs 正常的原因

问题场景

我正在开发一个以Firebase为后端的React应用,在useEffect中以activeChat为依赖项从Firestore获取聊天数据,activeChat包含用于唯一标识聊天的combinedId。

代码实现如下:

useEffect(() => {
  let ref = null;
  if (activeChat) {
    ref = collection(db, `directMessages/${activeChat.combinedId}/chats`)
  }

  let unsub = undefined;
  if (ref) {
    const q = query(ref, orderBy("createdAt"), limitToLast(6));
    unsub = onSnapshot(q, (snap) => {
      console.log("*****************")
      console.log("size =>", snap.docs.length)
      snap.docs.forEach(doc => console.log(doc.data()))
    })
  }
  return () => {
    unsub && unsub();
  }
}, [activeChat])

控制台输出显示,useEffect执行了两次(推测是因为应用运行在<React.StrictMode>下),但两次返回的查询结果不一致;而如果改用getDocs,useEffect仅执行一次,且能得到正确的结果(长度为6)。


原因分析

1. React StrictMode 的开发环境行为

在开发环境中,<React.StrictMode>会刻意重复执行副作用函数(比如useEffect的回调)和清理函数,目的是帮你发现潜在的代码问题,比如未正确清理的订阅、内存泄漏等。

对于你的代码来说,流程是:

  • 第一次执行useEffect,创建onSnapshot订阅,触发回调输出本地缓存的数据;
  • StrictMode立即调用清理函数(unsub()),取消第一次订阅;
  • 再次执行useEffect,重新创建onSnapshot订阅,此时已从服务器同步最新数据,触发回调输出正确结果。

这就导致了两次控制台输出的结果不一致。

2. onSnapshot 与 getDocs 的核心差异

  • onSnapshot(实时监听):它会先返回本地缓存中的现有数据快照,之后当服务器数据更新时,会再次触发回调。第一次订阅时,本地缓存可能还没同步到最新的6条数据,所以输出长度不符合预期;第二次订阅时,缓存已经更新,结果正确。
  • getDocs(单次读取):它直接请求并返回当前服务器(或最新缓存)的数据,即使StrictMode重复执行useEffect,两次调用获取的都是同一批最新数据,所以结果一致。另外,由于getDocs是异步单次请求,React在StrictMode下可能对这类无持久副作用的操作做了优化,让你看起来只执行了一次。

解决方案

  • 开发环境无需特殊处理:StrictMode的重复执行仅存在于开发环境,生产环境下不会触发,你的清理逻辑(unsub && unsub())已经正确,上线后不会有问题。
  • 若不需要实时更新:直接使用getDocs即可满足需求,但聊天场景通常需要实时同步,推荐保留onSnapshot。
  • 临时关闭StrictMode:如果开发时不想看到重复输出,可以暂时移除<React.StrictMode>包裹,但不推荐,会失去StrictMode的bug检测能力。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 11:22:35