You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firebase的onAuthStateChanged监听器移除后仍触发该如何正确卸载?

Firebase Auth状态监听器重复触发/无法移除解决方案

核心问题原因

当前代码的问题在于:

  • 你声明的unsubscribe是函数组件内的局部变量,每次组件渲染都会重置,无法持久化保存监听器的卸载方法
  • 仅在无关的useEffect清理函数中将其赋值为undefined,从来没有真正调用firebase.auth().onAuthStateChanged返回的卸载函数,导致监听器在启动页组件卸载后依然存活,登录页触发登录状态变更时,两个页面的监听器同时响应,出现重复导航、操作已卸载组件等崩溃问题
  • 没有清理延时器,延时结束后如果启动页已经卸载,调用setIsReady会触发React状态更新警告

修改步骤

  • 用useRef存储监听器卸载函数,保证跨渲染周期可以正常访问
  • 给注册监听器的useEffect添加清理函数,组件卸载/依赖变更时自动执行卸载逻辑
  • 补充延时器的清理逻辑,避免内存泄漏
  • 补充Firestore查询异常捕获,避免查询失败导致崩溃

修复后代码

import { useState, useEffect, useRef } from 'react'; // 确保导入useRef

try{
  if(!firebase.apps.length){
    firebase.initializeApp(firebaseConfig);
  }
}catch(err){
  console.log(err);
}
const cacheResourcesAsync = async () => {
    // 此处保留你原有加载图片的逻辑
    const images = [];
    const cacheImages = images.map(image => {
      return Asset.fromModule(image).downloadAsync();
    }); 
    return Promise.all(cacheImages);
}
export const SplashLoadingScreen = () => {
    const [isReady, setIsReady] = useState(false);
    const [isFire, setIsFire] = useState(false);
    const [isFont, setIsFont] = useState(false);
    const [isImages, setIsImages] = useState(false);
    const navigation = useNavigation();
    // 用useRef存储卸载函数
    const unsubscribeAuth = useRef(null);
    // 存储延时器id用于清理
    const splashTimerRef = useRef(null);

    //Loading Fonts
    const [michromaLoaded] = useMichroma({ Michroma_400Regular });
    const [latoLoaded] = useLato({ Lato_700Bold });

    //Checking if the fonts are ready
    useEffect(() => {
      if(michromaLoaded && latoLoaded){
        setIsFont(true);
      }
    }, [michromaLoaded, latoLoaded]);

    //Checking if the Images are loaded or not
    useEffect(() => {
      cacheResourcesAsync();
      setIsImages(true);    
    }, []);

    //Checking if Firebase is fired up
    useEffect(() => {
      if(firebase.apps.length !== 0){
        setIsFire(true);
      }
    }, [firebase.apps.length]);

    //Last Check before moving to other screen
    useEffect(() => {
      if(isFont && isImages && isFire){
        splashTimerRef.current = setTimeout(() => {
          setIsReady(true); // Animation Purpose
        }, 5000);
      }
      // 清理延时器
      return () => {
        if(splashTimerRef.current) {
          clearTimeout(splashTimerRef.current);
        }
      }
    }, [isFont, isImages, isFire]);   

    //Moving to other screens
    useEffect(() => {
      if(isReady){
        unsubscribeAuth.current = firebase.auth().onAuthStateChanged((user) => {          
          if (user) {
            firebase.firestore().collection("User Details").doc(user.uid).get().then((snap) => {
              if(snap.exists){
                navigation.navigate('Tab');
              }else {
                navigation.navigate('User');
              }
            }).catch(err => console.log('获取用户信息失败', err));
            return;
          }
          navigation.navigate("SignUp");
       });
      }
      // 核心:组件卸载/依赖变更时卸载监听器
      return () => {
        if(unsubscribeAuth.current) {
          unsubscribeAuth.current();
          unsubscribeAuth.current = null;
        }
      }
    }, [isReady, navigation]);

    SplashScreen.hideAsync();
    // 此处保留你原有渲染逻辑
}

额外注意

  • 登录页也需要按照相同的逻辑,给onAuthStateChanged监听器添加清理逻辑,避免同样的内存泄漏问题
  • 后续如果有多个页面都需要监听登录状态,建议将监听器抽离到全局状态管理(比如Context、Redux)中,统一管理监听逻辑,避免多页面重复注册监听器

内容的提问来源于stack exchange,提问作者Deep Togani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 22:36:03