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

React+Firestore应用短时间内5万次读取量异常排查

Firestore短时间内产生5万次读取量的问题排查

我正在开发一款React应用,实现从Firestore数据库展示成员、添加新成员及删除成员的功能。但仅我一人使用时,短短几分钟内就产生了5万次读取量,而该数据库集合仅占用几KB数据。

相关代码

React组件核心逻辑

import { useState, useEffect} from "react";
import { db } from "../utils/firebase-config";
import moment from "moment/moment";
import {
  collection,
  getDocs,
  addDoc,
  deleteDoc,
  doc
} from "firebase/firestore";

export default function MemberPage() {

    const membersCollectionRef =  collection(db, "members");

    // Create new member
    const [newName, setNewName] = useState("");
    const [newSurname, setNewSurname] = useState("");
    const [newNickname, setNewNickname] = useState("");
    const [newPhone, setNewPhone] = useState("");
    const [newEmail, setNewEmail] = useState("");
    const [newDateOfBirth, setNewDateOfBirth] = useState("");
    const [renderKey, setRenderKey] = useState(0);

    const createMember = async () => {
        await addDoc(membersCollectionRef, { 
            name: newName,
            surname: newSurname,
            nickname: newNickname,
            phone: newPhone,
            email: newEmail,
            dateOfBirth: newDateOfBirth,
            balance: 0
        });

        // Clear input fields values and refresh the component
        setNewName('');
        setNewSurname('');
        setNewNickname('');
        setNewPhone('');
        setNewEmail('');
        setNewDateOfBirth('');
        setRenderKey((prevKey) => prevKey + 1);
      };
    // Create new member

    // Read members from DB
    const [members, setMembers] = useState([]);
    
    const getMembers = async () => {
        const dataSnap = await getDocs(membersCollectionRef);
        setMembers(dataSnap.docs.map((doc) => ({ ...doc.data(), id: doc.id})));
    };

    useEffect(() => {
        getMembers();
    }, [membersCollectionRef, renderKey]);
    // Read members from DB

    // Delete members from DB
    const deleteMember = async (id) => {
        const memberDoc = doc(db, "members", id);
        await deleteDoc(memberDoc);
      };
    // Delete members from DB

成员列表渲染代码

{members.map((member) => { return (
                                <tr key={member.id}>
                                    <td>{member.name.toString()}</td>
                                    <td>{member.surname.toString()}</td>
                                    <td>{member.nickname.toString()}</td>
                                    <td>{member.phone.toString()}</td>
                                    <td>{member.email.toString()}</td>
                                    <td>{moment(Date(member.dateOfBirth.toString())).format('D/MM/YYYY')}</td>
                                    <td>{member.balance.toString()}</td>
                                    <td>
                                        <IconButton 
                                            aria-label="delete" 
                                            size="large" 
                                            color='error' 
                                            onClick={() => {
                                                deleteMember(member.id);
                                                }}>
                                            <DeleteIcon fontSize="inherit"/>
                                        </IconButton>
                                    </td>
                                    <td>update</td>
                                </tr>
                            );
                        })}

添加成员表单代码

<input 
                            type="text" 
                            value={newName}
                            placeholder="Imię"
                            onChange={(event) => {
                                setNewName(event.target.value);
                            }}
                        />
                        <input 
                            type="text"
                            value={newSurname}
                            placeholder="Nazwisko"
                            onChange={(event) => {
                                setNewSurname(event.target.value);
                            }}
                        />
                        <input 
                            type="text" 
                            value={newNickname}
                            placeholder="Ksywka"
                            onChange={(event) => {
                                setNewNickname(event.target.value);
                            }}
                        />
                        <input 
                            type="tel" 
                            value={newPhone}
                            placeholder="Telefon"
                            onChange={(event) => {
                                setNewPhone(event.target.value);
                            }}
                        />
                        <input 
                            type="email" 
                            value={newEmail}
                            placeholder="E-mail"
                            onChange={(event) => {
                                setNewEmail(event.target.value);
                            }}
                        />
                        <input 
                            type="date" 
                            value={newDateOfBirth}
                            placeholder="Data urodzenia"
                            onChange={(event) => {
                                setNewDateOfBirth(event.target.value);
                            }}
                        />
                    </div>
                    <div className="add-user">
                        <IconButton 
                            aria-label="add-user" 
                            size='large' 
                            color='primary' 
                            onClick={createMember}>
                            <PersonAdd sx={{fontSize: 60}}/>
                        </IconButton>
                    </div>

问题原因分析

1. 无限循环的useEffect触发重复读取

collection(db, "members")每次组件渲染时都会生成一个全新的引用对象,而你的useEffect依赖项包含这个引用,导致组件每次渲染都会触发useEffect执行,进而调用getMembers发起Firestore读取。

同时,输入框的onChange会频繁更新状态(比如setNewName),状态更新触发组件重新渲染,形成循环:输入内容 → 状态更新 → 组件渲染 → 新集合引用生成 → useEffect触发 → 读取Firestore → 更新members状态 → 再次渲染... 短时间内就会产生海量读取请求。

2. 删除成员后未同步本地状态

删除成员后,你既没有更新本地members状态,也没有触发renderKey变化,导致页面显示旧数据。后续若因其他原因触发渲染,会再次发起读取请求,进一步增加读取量。

3. 全量读取的模式不够高效

当前使用getDocs每次拉取全量数据,若需实时同步,这种方式会产生不必要的重复读取。

关于Firebase监控的疑问

打开Firebase使用监控本身不会增加读取量,它只是统计已发生的请求,高读取量完全由代码逻辑的重复请求导致。

修复方案

1. 解决useEffect无限循环

用useMemo缓存集合引用,避免每次渲染生成新对象:

import { useMemo } from "react";

const membersCollectionRef = useMemo(() => collection(db, "members"), [db]);

同时简化useEffect的依赖项:

useEffect(() => {
    getMembers();
}, [renderKey]); // 移除membersCollectionRef

2. 删除成员后同步本地状态

在deleteMember中删除成功后,直接过滤本地数据或触发刷新:

const deleteMember = async (id) => {
    const memberDoc = doc(db, "members", id);
    await deleteDoc(memberDoc);
    // 方案A:直接更新本地状态
    setMembers(prev => prev.filter(member => member.id !== id));
    // 方案B:或触发重新读取
    // setRenderKey(prev => prev + 1);
};

3. 改用实时监听优化读取模式

如果需要数据实时同步,替换getDocs为onSnapshot,只需要一次订阅,后续自动同步变化:

import { onSnapshot } from "firebase/firestore";

useEffect(() => {
    const unsubscribe = onSnapshot(collection(db, "members"), (snapshot) => {
        setMembers(snapshot.docs.map(doc => ({...doc.data(), id: doc.id})));
    });
    // 组件卸载时取消订阅
    return () => unsubscribe();
}, []);

这种方式无需手动管理renderKey,Firestore会主动推送数据变化,大幅减少读取次数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:27:09