React Redux中正确使用Firestore onSnapshot的方法问询
在React Redux中正确使用Firestore
onSnapshot 的方法 看起来你现在的写法可能有个小问题:onSnapshot 是Firestore的实时监听API,它并不返回一个Promise,而是会立即触发一次初始快照,之后每次数据变化都会触发回调,同时返回一个用于取消监听的函数。所以用async/await来处理它并不合适,这会导致你无法正确管理这个实时订阅,还可能引发内存泄漏。
下面是一套更规范的实现方案,兼顾Redux状态管理和Firestore实时监听的生命周期:
1. 调整Redux Action的写法
我们需要在action中创建监听,并且把取消监听的方法暴露出来,方便组件卸载时清理。同时,每次快照更新时dispatch对应的action来更新Redux状态:
// actions/checkinsActions.js export const FETCH_CHECKINS_START = 'FETCH_CHECKINS_START'; export const FETCH_CHECKINS_SUCCESS = 'FETCH_CHECKINS_SUCCESS'; export const FETCH_CHECKINS_ERROR = 'FETCH_CHECKINS_ERROR'; // 启动监听的action creator export const startListeningToCheckins = () => (dispatch) => { dispatch({ type: FETCH_CHECKINS_START }); const { currentUser } = firebaseService.auth(); if (!currentUser) { dispatch({ type: FETCH_CHECKINS_ERROR, payload: '用户未登录' }); return () => {}; // 返回空的清理函数 } // 计算今日起始时间戳 const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); // 建立Firestore实时监听 const unsubscribe = firebaseService.firestore() .collection(`checkins/${currentUser.uid}/dailyCheckins`) // 假设你的集合结构是这样 .where('timestamp', '>=', todayStart) .onSnapshot( (snapshot) => { const checkins = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); dispatch({ type: FETCH_CHECKINS_SUCCESS, payload: checkins }); }, (error) => { dispatch({ type: FETCH_CHECKINS_ERROR, payload: error.message }); } ); // 返回取消监听的函数,供组件清理使用 return unsubscribe; };
2. 对应的Reducer实现
处理这几个action类型,维护加载、数据、错误状态:
// reducers/checkinsReducer.js import { FETCH_CHECKINS_START, FETCH_CHECKINS_SUCCESS, FETCH_CHECKINS_ERROR } from '../actions/checkinsActions'; const initialState = { data: [], loading: false, error: null }; export default function checkinsReducer(state = initialState, action) { switch (action.type) { case FETCH_CHECKINS_START: return { ...state, loading: true, error: null }; case FETCH_CHECKINS_SUCCESS: return { ...state, loading: false, data: action.payload }; case FETCH_CHECKINS_ERROR: return { ...state, loading: false, error: action.payload }; default: return state; } }
3. 组件中正确使用(类组件)
不要用componentWillMount(这个生命周期钩子已经被标记为不安全),改用componentDidMount启动监听,componentWillUnmount时取消监听:
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { startListeningToCheckins } from '../actions/checkinsActions'; class CheckinsComponent extends Component { unsubscribeCheckins = null; componentDidMount() { // 调用action,获取取消监听的方法 this.unsubscribeCheckins = this.props.startListeningToCheckins(); } componentWillUnmount() { // 组件卸载时取消监听,防止内存泄漏 if (this.unsubscribeCheckins) { this.unsubscribeCheckins(); } } render() { const { loading, data, error } = this.props.checkins; if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error}</div>; return ( <div> <h3>今日签到记录</h3> {data.map(checkin => ( <div key={checkin.id}>{checkin.description}</div> ))} </div> ); } } const mapStateToProps = (state) => ({ checkins: state.checkins }); export default connect(mapStateToProps, { startListeningToCheckins })(CheckinsComponent);
4. 组件中正确使用(函数组件 + Hooks)
如果是函数组件,用useEffect的清理函数来处理取消监听:
import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { startListeningToCheckins } from '../actions/checkinsActions'; function CheckinsComponent({ checkins, startListeningToCheckins }) { useEffect(() => { const unsubscribe = startListeningToCheckins(); // 清理函数:组件卸载时取消监听 return () => unsubscribe(); }, [startListeningToCheckins]); const { loading, data, error } = checkins; if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error}</div>; return ( <div> <h3>今日签到记录</h3> {data.map(checkin => ( <div key={checkin.id}>{checkin.description}</div> ))} </div> ); } const mapStateToProps = (state) => ({ checkins: state.checkins }); export default connect(mapStateToProps, { startListeningToCheckins })(CheckinsComponent);
关键注意点
- 不要用async/await处理onSnapshot:它不是一次性的异步操作,而是持续的订阅,返回的unsubscribe函数是关键。
- 必须取消监听:组件卸载时一定要调用unsubscribe,否则会一直监听数据变化,导致内存泄漏和不必要的资源消耗。
- 处理用户登录状态:如果currentUser可能为null(比如用户未登录或auth状态还未初始化),要在action中做判断,避免报错。
- 状态管理完整性:要维护加载、错误状态,提升用户体验。
内容的提问来源于stack exchange,提问作者Jakob
相关产品推荐
相关产品推荐

