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

为什么React组件渲染4次,从Firestore获取的menus存在多个空数组?

问题原因及修复方案

核心问题点

  • 你在注册onSnapshot实时监听器之后立刻同步调用了unsubscribe(),监听器刚注册就被销毁,逻辑完全错误。虽然你最后偶然拿到了数据,但这个写法本身会导致数据获取不可靠。
  • setLoading的更新时机错误:onSnapshot的回调是异步执行的,你同步在注册监听器之后就设置setLoading(false),等于还没拿到数据就已经结束了加载状态。
  • useEffect没有配置清理函数,组件卸载时无法正确销毁监听器,可能引发内存泄漏。
  • 渲染逻辑没有加加载状态、空数据判断,所以空数组阶段会直接渲染空白内容。
  • 控制台多次打印空数组是正常的React渲染触发:组件初始化渲染1次、调用getMenu时setLoading(true)触发第2次渲染、加上路由/父组件渲染触发第3次,最后拿到数据调用setMenus触发第4次渲染,刚好对应你看到的3次空数组、1次有数据的打印结果。

修复后的代码

import { collection, onSnapshot, orderBy, query } from 'firebase/firestore';
import React, { useState, useEffect, Fragment } from 'react'
import { db } from '../../firebase'

export default function Speisekarte() { 
    const [menus, setMenus] = useState([]);
    const [loading, setLoading] = useState(false);

    console.log(menus);

    useEffect(() => {
        setLoading(true)
        const docRef = query(collection(db, "meals"), orderBy("id", "asc"));
        // 注册监听器,拿到取消方法
        const unsubscribe = onSnapshot(docRef, (querySnapshot) => {
            const items = [];
            querySnapshot.forEach((doc) => {
                items.push(doc.data());
            });
            setMenus(items);
            // 拿到数据后再关闭加载状态
            setLoading(false);
        });
        // 组件卸载时执行清理,取消监听器
        return () => unsubscribe();
    },[]);

    // 加载中显示占位内容
    if (loading) return <div className="menu"><div className="menu__container">加载中...</div></div>;
    // 没有数据时显示空提示,避免渲染空白
    if (menus.length === 0) return <div className="menu"><div className="menu__container">暂无菜单数据</div></div>;

    return (
        <section className="menu">
          <div className="menu__container">
            <h1 className="menu__container__title">Menu</h1>
            {menus.map((item) => {
              const keys = Object.keys(item);
              return (
                <Fragment key={item.title}>
                        <h2>{item.title}</h2>
                        <div className="menu__container__item">
                        {keys.map((gericht) => (
                            <article className="menu__container__item-article" key={item[gericht].id}>
                                <div className="menu__container__item-article__row">
                                    <h3 className="menu__container__item-article__title">{item[gericht].title}</h3>
                                    <span className="menu__container__item-article__price">{item[gericht].price}</span>
                                </div>
                                <p className="menu__container__item-article__info">
                                    {item[gericht].desc}
                                </p>
                            </article>
                        ))}
                    </div>
                  
                </Fragment>
              );
            })}
          </div>
        </section>
      );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 14:48:03