React项目使用Firestore的最优实现方式是什么?现有方案求优化建议
现有实现存在的问题
- 错误处理缺失:
getMyData中catch仅打印日志,错误场景会返回undefined,写入Redux后会污染全局状态,且自定义Hook未暴露错误状态,组件无法感知请求失败给用户反馈 - 重复请求风险:多个组件同时调用
useGetData时,若Redux中还未缓存数据,会触发多次重复的Firestore请求,浪费带宽 - 能力浪费:Firestore原生支持实时数据监听,当前用
getDocs实现的是一次性拉取,后端数据变更后前端无法自动同步,只能手动触发重查 - 状态管理冗余:你本身熟悉react-query,当前手写的加载状态、重查逻辑、缓存判断react-query都已内置,自行维护很容易出现边界问题(比如请求中组件卸载导致的内存泄漏)
- 细节问题:useEffect内部定义的
data变量和外层Redux返回的data同名,可读性差;拉取结果仅取doc.data()未携带文档ID,后续需要修改/删除单条数据时无法直接操作
优化建议
方案1:保留Redux的最小改动版本
首先优化数据拉取函数,补充错误抛出和文档ID返回:
// Function to get the Data using Firebase SDK export const getMyData = async () => { try { const res = await getDocs(collection(db, `path/to/data`)) // 携带文档ID,方便后续操作单条数据 return res.docs.map(doc => ({ id: doc.id, ...doc.data() })) } catch (e) { console.error('拉取数据失败:', e) // 把错误抛出给上层处理 throw e } };
再优化自定义Hook,处理竞态、组件卸载边界和错误状态:
// Custom React Hook that fetches the data , set the loading state and dispatch the results to the redux store export const useGetData = () => { const dispatch = useAppDispatch(); // 重命名避免和内部变量冲突 const reduxData = useAppSelector(state => state.data); const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); const [refetchTrigger, setRefetchTrigger] = React.useState(false); const refetch = () => setRefetchTrigger(true); React.useEffect(() => { // 标记组件是否卸载,避免卸载后更新状态导致内存泄漏 let isUnmounted = false; const getdata = async () => { if (!reduxData || refetchTrigger) { setRefetchTrigger(false); setLoading(true); setError(null); try { const data = await getMyData(); if (!isUnmounted) { dispatch(setdata(data)); } } catch(e) { if (!isUnmounted) { setError(e); } } finally { if (!isUnmounted) { setLoading(false); } } } }; getdata(); return () => { isUnmounted = true }; }, [reduxData, refetchTrigger, dispatch]); return { data: reduxData, loading, error, refetch }; };
方案2:使用react-query重构(更推荐)
你本身熟悉react-query,用它替代手写的请求逻辑可以减少一半以上的代码量,还能自动获得缓存、请求去重、失败重试、后台刷新等能力,非全局通用的业务数据也不用额外存到Redux里:
import { useQuery } from '@tanstack/react-query' export const useGetData = () => { return useQuery({ // 全局唯一缓存key,多个组件调用同个Hook只会发起一次请求 queryKey: ['myFirebaseData'], queryFn: getMyData, // 可选配置,可根据业务调整 staleTime: 5 * 60 * 1000, // 5分钟内数据视为新鲜,不用重复拉取 retry: 2, // 请求失败自动重试2次 }) } // 组件内直接调用即可,不用对接Redux const DemoComponent = () => { const { data, isLoading, error, refetch } = useGetData() // 直接使用返回的状态渲染即可 }
如果需要用到Firestore的实时更新能力,可直接改用snapshot监听实现,无需手动触发重查:
export const useRealtimeMyData = () => { const dispatch = useAppDispatch() const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { // 开启实时监听 const unsubscribe = onSnapshot( collection(db, 'path/to/data'), (snapshot) => { const data = snapshot.docs.map(doc => ({id: doc.id, ...doc.data()})) dispatch(setdata(data)) setLoading(false) }, (err) => { setError(err) setLoading(false) } ) // 组件卸载时取消监听,避免内存泄漏 return () => unsubscribe() }, [dispatch]) return { data: useAppSelector(state => state.data), loading, error } }
内容的提问来源于stack exchange,提问作者Hiimdjango
相关产品推荐
相关产品推荐

