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
相关产品推荐
相关产品推荐

