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

