React Native (Expo)中如何等待Firebase onSnapshot加载数据避免重复请求
核心解决思路
把onSnapshot的首次回调包装为Promise,替代单独的get()请求,既可以在AppLoading中等待加载完成,又不会重复触发数据拉取。
改造步骤
1. 改造订阅Hook,返回初始化Promise
修改useInitFirebaseSubscriptions,将首次onSnapshot回调的完成状态通过Promise暴露给外部:
import { useRef, useEffect } from 'react'; import { useDispatch } from 'react-redux'; export function useInitFirebaseSubscriptions() { const dispatch = useDispatch(); // 存储Promise的resolve、reject方法 const initResolveRef = useRef(); const initRejectRef = useRef(); // 生成初始化Promise供外部等待 const initPromise = useRef(new Promise((resolve, reject) => { initResolveRef.current = resolve; initRejectRef.current = reject; })).current; useEffect(() => { console.log('[CONTROLLER]: subscribed to Firebase'); let isFirstLoad = true; const unsubscribe = firestore() .collection('users') .onSnapshot( (querySnapshot) => { dispatch(action.fetch(querySnapshot)); // 仅首次加载完成时resolve Promise if (isFirstLoad) { isFirstLoad = false; initResolveRef.current(); } }, (error) => { console.log(error); // 首次加载失败时reject Promise if (isFirstLoad) { isFirstLoad = false; initRejectRef.current(error); } } ); return () => { unsubscribe(); console.log('[CONTROLLER]: unsubscribed from Firebase'); }; }, []); return initPromise; }
2. 改造Manager组件,移除冗余拉取逻辑
删除原来的useFetchData调用,直接等待订阅的初始化Promise完成:
const Manager = (props) => { //STATE const [init, setInit] = useState(false); //HOOKS - 拿到订阅的初始化Promise const firebaseInitPromise = useInitFirebaseSubscriptions(); //FUNCTIONS async function onInit() { console.log('[MANAGER]: loading app...'); // 等待订阅首次加载完成即可,无需额外拉取数据 await firebaseInitPromise; } function onFinishedInit() { console.log('[MANAGER]: ...app loading successfull!'); setInit(true); } //RETURN if (!init) { return <AppLoading startAsync={onInit} onFinish={onFinishedInit} onError={console.warn} />; } else { return props.children; } }; export default Manager;
3. 删除冗余的useFetchData相关代码即可。
效果验证
改造后控制台只会输出一次[USER_REDUCER]: fetched data,不会重复请求,同时AppLoading会正常等待数据加载完成后再渲染主页面,不会出现闪动。
内容的提问来源于stack exchange,提问作者sexpomicolon
相关产品推荐
相关产品推荐

