React搭配Firebase Firestore开发时数据仅在第二/三次渲染才成功获取
问题根源
你的代码问题出在两个useEffect的依赖项设置错误、执行时序不匹配,和加载态判断逻辑错误:
- 两个
useEffect都只在组件首次挂载时触发,属于并行执行,第二个查询books的useEffect运行时,第一个请求用户数据的接口还没返回,userBookTitles还是初始的空数组,所以第一次查询无结果 - 你在第二个
useEffect里直接把loading设为false,此时用户数据和书籍数据都还没加载完成,所以页面提前渲染了空数据 - 等第一个接口拿到用户数据更新
userBookTitles后,第二个useEffect没有监听这个变量,不会重新触发查询,所以要多次渲染才能拿到数据
修复方案
- 调整第二个
useEffect的依赖项为userBookTitles,只有当userBookTitles有值时才触发书籍查询 - 把加载态关闭的时机移到书籍数据查询完成后
- 如果不需要书籍数据实时更新,可以不用
onSnapshot,改用一次性的get方法即可;如果要用onSnapshot,需要添加清理函数取消监听,避免内存泄漏
修改后参考代码
import 'firebase/firestore' import { useState, useEffect } from 'react' import { booksRef} from '../../App'; const ProfileScreen = ({ navigation }) => { const currentUserUID = firebase.auth().currentUser.uid; const [firstName, setFirstName] = useState(''); const [userBookTitles, setUserBookTitles] = useState([]); const [userBooks, setUserBooks] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { async function getUserInfo(){ let doc = await firebase .firestore() .collection('users') .doc(currentUserUID) .get(); if (!doc.exists){ Alert.alert('No user data found!') setLoading(false) } else { let dataObj = doc.data(); setFirstName(dataObj.firstName) setUserBookTitles(dataObj.books || []) } } getUserInfo(); }, [currentUserUID]) useEffect(() => { // 只有用户的书籍列表有值时才查询书籍集合 if(userBookTitles.length === 0) return // 用onSnapshot的情况,添加清理函数 const unsubscribe = booksRef.where("title", "in", userBookTitles).onSnapshot(snapshot => { setUserBooks(snapshot.docs.map((doc) => ({id: doc.id, ...doc.data()}))) setLoading(false) // 书籍数据拿到后再关加载 }) // 组件卸载时取消监听 return () => unsubscribe() // 不需要实时更新的话可以用get方法: /* const getUserBooks = async () => { const snapshot = await booksRef.where("title", "in", userBookTitles).get() setUserBooks(snapshot.docs.map(doc => ({id: doc.id, ...doc.data()}))) setLoading(false) } getUserBooks() */ }, [userBookTitles]) if (!loading) { return ( <View style={styles.container}> <Text> Hi {firstName} </Text> <TouchableOpacity onPress={handlePress}> <Text> Log out </Text> </TouchableOpacity> <Row books={userBooks} /> </View> ); } else { return ( <View style={styles.container}> <Text> 加载中 </Text> </View> ); } };
内容的提问来源于stack exchange,提问作者Dybton
相关产品推荐
相关产品推荐

