React+Firestore:用户加入活动后如何重新获取费用数据?
我有一个包含user、event和expense集合的Firestore数据库:
- 每个
event关联多名参与用户 - 每个
expense链接到特定活动
初始逻辑:用户访问非参与活动时,该活动的费用会被隐藏,显示“加入”按钮;用户点击按钮成为参与者后,event文档更新,用户获得该活动费用的访问权限。
使用React+Firestore开发时遇到的核心问题:用户加入活动后,由于expense集合本身未发生变更,费用不会自动显示,需要实现无需页面刷新就能重新获取expenses查询结果。
简化后的React代码:
function Event() { const [user] = useAuthState(fbAuth); const { eventId } = useParams(); const [event, eventLoading] = useGetEvent(eventId as string); const eventData = event.data(); const isUserParticipating = user && eventData?.participantsIds?.includes(user?.uid); const [expenses, loading, error] = useGetEventExpenses(eventId as string); useEffect(() => { // 如何在用户加入活动时触发重新获取expenses查询,让费用无需刷新页面就显示? }, [isUserParticipating]); // ...jsx }
Firestore安全规则:
service cloud.firestore { match /databases/{database}/documents { // 仅允许已认证用户写入event集合 match /event/{eventId} { allow read: if true; allow write: if request.auth != null; } match /user/{userId} { allow read: if true; allow write: if request.auth.uid == userId; } // 用户仅能读写自己参与的活动的费用 match /expense/{expenseId} { allow create: if request.auth.uid in get(/databases/$(database)/documents/event/$(request.resource.data.parentEventId)).data.participantsIds; allow delete,read: if request.auth.uid in get(/databases/$(database)/documents/event/$(resource.data.parentEventId)).data.participantsIds; } } }
核心疑问:当决定资源访问权限的其他文档(此处为event文档)发生变更时,如何更新Firestore快照监听器?
方法1:让自定义钩子依赖参与状态,自动重启监听器
修改useGetEventExpenses钩子,将isUserParticipating作为依赖项。当参与状态变化时,自动取消旧监听器并创建新的查询监听器。
修改后的useGetEventExpenses示例:
function useGetEventExpenses(eventId, isUserParticipating) { const [expenses, setExpenses] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!eventId || !isUserParticipating) { setExpenses([]); setLoading(false); return; } // 创建费用查询监听器 const unsubscribe = db.collection('expense') .where('parentEventId', '==', eventId) .onSnapshot( (snapshot) => { const expensesList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setExpenses(expensesList); setLoading(false); }, (err) => { setError(err); setLoading(false); } ); // 组件卸载或依赖变化时取消监听器 return unsubscribe; }, [eventId, isUserParticipating]); return [expenses, loading, error]; }
在Event组件中调用时传入参与状态:
const [expenses, loading, error] = useGetEventExpenses(eventId as string, isUserParticipating);
当isUserParticipating从false变为true时,钩子会自动重新创建监听器,获取用户有权限访问的费用数据。
方法2:手动触发查询刷新(适配支持手动刷新的钩子)
如果useGetEventExpenses钩子可以暴露手动刷新方法,可在useEffect中直接调用:
首先修改钩子添加刷新函数:
function useGetEventExpenses(eventId) { const [expenses, setExpenses] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchExpenses = useCallback(async () => { setLoading(true); try { const snapshot = await db.collection('expense') .where('parentEventId', '==', eventId) .get(); const expensesList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setExpenses(expensesList); } catch (err) { setError(err); } finally { setLoading(false); } }, [eventId]); // 初始加载 useEffect(() => { if (eventId) fetchExpenses(); }, [eventId, fetchExpenses]); return [expenses, loading, error, fetchExpenses]; }
在Event组件中触发刷新:
const [expenses, loading, error, fetchExpenses] = useGetEventExpenses(eventId as string); useEffect(() => { // 用户刚加入活动时,手动触发费用查询 if (isUserParticipating) { fetchExpenses(); } }, [isUserParticipating, fetchExpenses]);
方法3:监听event文档变化,自动触发费用查询
利用用户加入活动时event文档会更新的特性,在useGetEventExpenses中同时监听event文档的变化,一旦participantsIds更新,就重新获取费用:
function useGetEventExpenses(eventId) { const [expenses, setExpenses] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [user] = useAuthState(fbAuth); const fetchExpenses = useCallback(async () => { if (!user) return; setLoading(true); try { const snapshot = await db.collection('expense') .where('parentEventId', '==', eventId) .get(); const expensesList = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setExpenses(expensesList); } catch (err) { setError(err); } finally { setLoading(false); } }, [eventId, user]); useEffect(() => { if (!eventId || !user) return; // 监听event文档的变化 const unsubscribeEvent = db.collection('event').doc(eventId) .onSnapshot(() => { // 每次event文档更新,重新获取费用 fetchExpenses(); }); // 初始加载费用 fetchExpenses(); return () => unsubscribeEvent(); }, [eventId, user, fetchExpenses]); return [expenses, loading, error]; }
这种方式无需依赖外部的isUserParticipating状态,直接通过监听event文档变化触发查询,更贴合Firestore的实时特性。
内容的提问来源于stack exchange,提问作者Eliav Louski

