如何在不触发重渲染的情况下即时加载Firebase实时数据库中的所有条目?
解决Firebase实时数据库多次重渲染才加载完整设备数据的问题
我来帮你分析下问题所在,然后给出针对性的解决方案——你遇到的多次重渲染问题,本质是异步逻辑的执行顺序和监听时机没处理对,咱们一步步来解决:
你的场景与问题
在使用Firebase实时数据库的应用中,你需要加载与用户关联的所有设备数据(用户可查看的设备由其所属公司cid决定)。按照Firebase v9文档编写代码后,却需要经过3次重渲染才能拿到正确的设备数据,尝试异步使用get命令也没有改善。
你的代码示例
useEffect(() => { const auth = getAuth(); const db = getDatabase(); const userRef = ref(db, "users/" + auth.currentUser.uid + "/cid"); onValue(userRef, (snapshot) => { const cid = snapshot.val(); setCid(cid, true); }); const machinesRef = ref(db, "companies/" + cid + "/machines"); onValue(machinesRef, (snapshot) => { const fetchedData = []; snapshot.forEach((childSnapshot) => { const childKey = childSnapshot.key; const childData = childSnapshot.val(); const parsRef = ref(db, "machines/" + childKey); onValue(parsRef, (snapshot) => { const parData = snapshot.val(); fetchedData.push( new Machine( childKey, cid, childData.type, childData.creation, parData.temperature ) ); }); }); setData(fetchedData); }); console.log(cid); // 输出至结果区域 console.log(data); // 输出至结果区域 }, []);
执行结果
渲染1:null 数组[]
渲染2:cid(正确值) 数组[]
渲染3:cid 数组[
Machine { "companyId": "cid", "creation": "creation", "id": "23rff3345GRR", "temperature": 99, "type": "type" },
Machine { "companyId": "cid", "creation": "creation", "id": "3454egGR3", "temperature": 2, "type": "type" }
]
问题根源分析
- 异步逻辑顺序错误:你同时启动了用户
cid的监听和设备数据的监听,但设备监听依赖的cid初始值为null,导致machinesRef指向无效路径companies/null/machines,自然拿不到数据。而你的useEffect依赖是空数组,设备监听代码不会在cid更新后重新执行。 - 嵌套监听时机不对:在设备列表的
snapshot.forEach里直接用onValue监听单设备详情,这个回调是异步的——当你调用setData(fetchedData)时,这些异步回调还没执行完,所以第一次设置的fetchedData是空的,后续回调触发后才会填充数据,进而触发额外重渲染。
解决方案
方案1:嵌套监听+清理订阅(适合实时更新场景)
如果需要实时监听设备数据变化,要把设备监听放在cid获取成功的回调里,同时清理所有订阅防止内存泄漏:
useEffect(() => { const auth = getAuth(); const db = getDatabase(); let unsubscribeUser; let unsubscribeMachines; const machineUnsubscribes = []; // 先监听用户的cid const userRef = ref(db, `users/${auth.currentUser.uid}/cid`); unsubscribeUser = onValue(userRef, (snapshot) => { const cid = snapshot.val(); if (!cid) return; // cid为空直接返回 setCid(cid); // 清理之前的设备相关监听 if (unsubscribeMachines) unsubscribeMachines(); machineUnsubscribes.forEach(unsub => unsub()); machineUnsubscribes.length = 0; // cid有效后,监听公司设备列表 const machinesRef = ref(db, `companies/${cid}/machines`); unsubscribeMachines = onValue(machinesRef, (machinesSnapshot) => { const fetchedData = []; machinesSnapshot.forEach((childSnapshot) => { const childKey = childSnapshot.key; const childData = childSnapshot.val(); // 监听单设备详情 const parsRef = ref(db, `machines/${childKey}`); const unsubPar = onValue(parsRef, (parSnapshot) => { const parData = parSnapshot.val(); // 避免重复添加,更新已有设备或新增 const existingIndex = fetchedData.findIndex(m => m.id === childKey); if (existingIndex !== -1) { fetchedData[existingIndex] = new Machine( childKey, cid, childData.type, childData.creation, parData.temperature ); } else { fetchedData.push(new Machine( childKey, cid, childData.type, childData.creation, parData.temperature )); } setData([...fetchedData]); // 浅拷贝触发状态更新 }); machineUnsubscribes.push(unsubPar); }); }); }); // 组件卸载时清理所有订阅 return () => { if (unsubscribeUser) unsubscribeUser(); if (unsubscribeMachines) unsubscribeMachines(); machineUnsubscribes.forEach(unsub => unsub()); }; }, []);
方案2:使用get替代onValue(适合一次性加载数据场景)
如果不需要实时监听,只是一次性加载数据,用get(异步获取单份数据)配合Promise.all,等待所有设备详情加载完成后再更新状态,减少重渲染:
useEffect(() => { const fetchData = async () => { const auth = getAuth(); const db = getDatabase(); // 1. 获取用户cid const userRef = ref(db, `users/${auth.currentUser.uid}/cid`); const userSnapshot = await get(userRef); const cid = userSnapshot.val(); if (!cid) return; setCid(cid); // 2. 获取公司设备列表 const machinesRef = ref(db, `companies/${cid}/machines`); const machinesSnapshot = await get(machinesRef); // 3. 批量获取所有设备详情 const fetchPromises = []; machinesSnapshot.forEach((childSnapshot) => { const childKey = childSnapshot.key; const childData = childSnapshot.val(); const parsRef = ref(db, `machines/${childKey}`); fetchPromises.push( get(parsRef).then(parSnapshot => { const parData = parSnapshot.val(); return new Machine( childKey, cid, childData.type, childData.creation, parData.temperature ); }) ); }); // 等待所有请求完成后一次性更新状态 const fetchedData = await Promise.all(fetchPromises); setData(fetchedData); }; fetchData(); }, []);
关键优化点
- 避免无效请求:确保拿到有效
cid后再请求设备数据; - 清理订阅:使用
onValue时必须保存并清理unsubscribe函数,防止内存泄漏; - 一次性更新状态:用
Promise.all等待所有异步操作完成后再更新状态,减少不必要的重渲染。
内容的提问来源于stack exchange,提问作者Rubinjo
相关产品推荐
相关产品推荐

