如何在React中用新版Firebase/Firestore获取集合及子集合数据?
适配新版Firebase SDK:如何获取Firestore子集合数据并关联到父文档对象
我在跟着6个月前YouTube上的Netflix克隆项目教程学习,但由于Firebase/Firestore和React推出了新版本,原教程代码无法运行。
原教程要实现的功能:
- 访问
Products集合,为每个产品文档创建对应的Product对象 - 进入每个产品文档的
Prices子集合,获取价格文档数据并添加到对应的Product对象中 - 最终将包含价格数据的Product对象打印到控制台
我已经重构了代码以兼容新版SDK,现在能在控制台打印Product对象,但缺少Prices子集合的数据。以下是原教程代码和我的重构代码:
原教程代码
import React, { useState, useEffect } from 'react'; import db from "../firebase"; import './PlansScreen.css'; function PlansScreen() { const [products, setProducts] = useState([]); useEffect(() => { db.collection("products") .where("active", "==", true) .get() .then((querySnapshot) => { const products = {}; querySnapshot.forEach(async (productDoc) => { products[productDoc.id] = productDoc.data(); const priceSnap = await productDoc.ref.collection("prices").get(); priceSnap.docs.forEach((price) => { products[productDoc.id].prices = { priceId: price.id, priceData: price.data(), }; }); }); setProducts(products); }); }, []); console.log(products) return ( <div className='plansScreen'> </div> ) } export default PlansScreen
我的重构代码
import React, { useState, useEffect } from 'react'; import db, { collection, getDocs } from "../firebase"; import './PlansScreen.css'; function PlansScreen() { const [products, setProducts] = useState([]); useEffect(() => { const get = async () => { const prodRef = collection(db, 'products'); const prodSnap = await getDocs(prodRef); prodSnap.forEach(async (prodDoc) => { products[prodDoc.id] = prodDoc.data(); }); setProducts(products); }; get(); }, [products]); console.log(products) return ( <div className='plansScreen'> </div> ) } export default PlansScreen
我尝试了多种方法,但始终无法在创建Product对象时获取并关联子集合的数据,希望能得到适配新版SDK的解决方案。
解决方案
你的核心问题有两个:
forEach不支持异步函数,导致子集合数据还没获取完就更新了状态- 直接修改React state对象,且useEffect依赖错误导致潜在的无限循环
以下是修复后的完整代码:
import React, { useState, useEffect } from 'react'; import db, { collection, getDocs, query, where } from "../firebase"; import './PlansScreen.css'; function PlansScreen() { const [products, setProducts] = useState({}); // 改为对象存储,和原教程逻辑一致 useEffect(() => { const fetchProductsWithPrices = async () => { // 保留原教程的active筛选条件 const prodQuery = query(collection(db, 'products'), where("active", "==", true)); const prodSnap = await getDocs(prodQuery); // 用map+Promise.all处理异步操作,确保所有子集合数据都获取完成 const productsData = await Promise.all( prodSnap.docs.map(async (prodDoc) => { const productData = prodDoc.data(); // 获取当前产品的prices子集合 const pricesRef = collection(prodDoc.ref, 'prices'); const pricesSnap = await getDocs(pricesRef); // 处理价格数据(这里和原教程逻辑一致,只保留最后一个价格,或可改为数组) let productPrices = {}; pricesSnap.docs.forEach(price => { productPrices = { priceId: price.id, priceData: price.data() }; }); // 返回包含价格的产品对象,键为文档ID return { [prodDoc.id]: { ...productData, prices: productPrices } }; }) ); // 将所有产品对象合并为一个大对象 const mergedProducts = productsData.reduce((acc, curr) => ({ ...acc, ...curr }), {}); setProducts(mergedProducts); }; fetchProductsWithPrices(); }, []); // 空依赖数组,只执行一次 console.log(products); return ( <div className='plansScreen'> </div> ) } export default PlansScreen
关键修复点说明:
- 异步处理:用
Promise.all配合map代替forEach,确保所有子集合的异步请求完成后再更新状态 - 子集合获取:新版SDK中,通过
collection(prodDoc.ref, 'prices')创建子集合引用,再用getDocs获取数据 - 状态更新:创建新的对象存储产品数据,避免直接修改state;合并所有产品后再调用
setProducts - 筛选条件:恢复原教程的
where("active", "==", true)筛选,只获取活跃产品 - 依赖优化:useEffect用空数组作为依赖,避免无限循环
现在运行代码,控制台打印的Product对象应该会包含对应的Prices子集合数据。
内容的提问来源于stack exchange,提问作者Cameron Sickler
相关产品推荐
相关产品推荐

