React对接Firestore实时数据触发过量调用致应用崩溃问题求助
问题排查与修复方案
核心问题定位
- 你在
RuuviTag.map渲染逻辑中直接调用checkBoundaries方法,该方法每次执行都会为指定MAC的Firestore文档注册一个全新的onSnapshot实时监听器,且从未做卸载清理。只要组件重渲染(比如state更新触发重绘),就会新增一批监听器,监听器数量指数级上涨,自然会产生海量日志、频繁拉取数据最终导致应用崩溃。 - 你已经通过
getData方法监听了整个RuuviTag集合的实时变更,再给每个文档单独加监听器属于完全冗余的重复操作,没有任何必要。 responseData、mapIcon是组件全局共享的state,所有标签会共用同一份数据,无法实现单个标签根据自身温度单独变色的需求。axios.post是异步请求,你在请求发出后立刻读取responseData,此时请求还未返回,拿到的永远是旧值,温度判断逻辑完全失效。- 没有对Firestore监听器做卸载处理,组件销毁后监听器仍然在运行,会造成内存泄漏。
修复代码
import './LiveRouteTile.scss'; import { useState, useEffect } from 'react'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import { Icon } from 'leaflet' import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet' import firebase from "../../../firebase" import axios from "axios"; // 图标地址抽成常量避免重复声明 const RED_ICON = 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png'; const BLUE_ICON = 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-blue.png'; const LiveRouteTile = ({ type }) => { const [tagList, setTagList] = useState([]); const [loading, setLoading] = useState(false); // 存储每个MAC对应的接口返回数据和图标,避免全局共享 const [tagMetaMap, setTagMetaMap] = useState({}); // 处理单条tag的接口请求和温度判断 const processTagData = async (tag) => { try { const res = await axios.post("https://localhost:44332/Convert/", { rawData: tag.RawData }); const temp = res.data.temperature; const icon = temp > 4 && temp < 22 ? BLUE_ICON : RED_ICON; // 只更新当前MAC对应的元数据 setTagMetaMap(prev => ({ ...prev, [tag.MAC]: { data: res.data, icon } })); } catch (err) { console.error(`处理MAC为${tag.MAC}的标签数据失败:`, err); } }; useEffect(() => { setLoading(true); const collectionRef = firebase.firestore().collection("RuuviTag"); // 保存监听器取消函数 const unsubscribe = collectionRef.onSnapshot((querySnapshot) => { const items = []; querySnapshot.forEach((doc) => { const tagData = doc.data(); items.push(tagData); // 集合数据更新时处理当前tag的请求逻辑,无需单独监听文档 processTagData(tagData); }); setTagList(items); setLoading(false); }); // 组件卸载时取消监听器,避免内存泄漏 return () => unsubscribe(); }, []); return ( <div className="route-tile"> <Card className="route-card" > <CardContent className="route-card-content"> {loading ? <h1>Loading...</h1> : ( <MapContainer className="map" center={[52.09, 5.10]} zoom={8}> <TileLayer attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> {tagList.map((tag) => { const meta = tagMetaMap[tag.MAC] || { icon: RED_ICON }; return ( <div key={tag.MAC}> <Marker position={[tag.Latitude.replace(/,/g, '.'), tag.Longitude.replace(/,/g, '.')]} icon={new Icon({ iconUrl: meta.icon, iconSize: [25, 41], iconAnchor: [12, 41] })} > <Popup> <h1>{tag.MAC}</h1> {meta.data && ( <div> <p>温度:{meta.data.temperature}℃</p> <p>湿度:{meta.data.humidity}%</p> </div> )} </Popup> </Marker> </div> ) })} </MapContainer> )} </CardContent> </Card> </div> ) } export default LiveRouteTile
优化点说明
- 移除了单文档的重复监听器,仅保留集合级别的实时监听,大幅降低Firestore请求量
- 每个标签的接口返回数据、图标状态独立存储,互不影响
- 所有副作用逻辑全部移到
useEffect中,不会在渲染时重复执行 - 新增了监听器清理逻辑,避免内存泄漏
- 异步请求使用
async/await改写,逻辑更清晰,不会出现时序问题
内容的提问来源于stack exchange,提问作者Jaap Smit
相关产品推荐
相关产品推荐

