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

Expo React Native中Android/iOS端登出触发TypeError错误

问题

我开发的Expo React Native应用,网页端用户登出功能正常,但在Android和iOS端执行登出操作时,出现错误:TypeError: undefined is not a function,错误定位在BottomNavigation相关组件中。

用户首次进入应用及登录后流程均正常,已通过Firebase的onAuthStateChanged监听登录状态变化,状态变量loggedIn设置正确,但仍出现该登出错误。

App.js(省略导入部分)

const store = configureStore({ reducer: rootReducer })

const Stack = createStackNavigator()
const Drawer = createDrawerNavigator()

const App = ({ title }) => {
  const [loggedIn, setLoggedIn] = useState(false)
  const [loaded, setLoaded] = useState(false)

  const navigation = useNavigation()

  const openDrawer = () => {
    navigation.dispatch(DrawerActions.toggleDrawer())
  }

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(user => {
      if (!user) {
        setLoggedIn(false)
        setLoaded(true)
      } else {
        setLoggedIn(user.emailVerified)
        setLoaded(true)
      }
    })
    return unsubscribe
  }, [])

  if (!loaded) {
    return <Loader loading={loaded} />
  }

  return !loggedIn ? (
    <Stack.Navigator initialRouteName="Login">
      <Stack.Screen
        name="Register"
        component={RegisterScreen}
        options={{ headerShown: false }}
      />
      <Stack.Screen
        name="Login"
        component={LoginScreen}
        options={{ headerShown: false }}
      />
      <Stack.Screen
        name="ForgotPassword"
        component={ForgotPasswordScreen}
        options={{ headerShown: false }}
      />
    </Stack.Navigator>
  ) : (
    <Drawer.Navigator
      useLegacyImplementation
      drawerContent={props => <LogOut {...props} />}
      initialRouteName="Main"
      screenOptions={{
        headerLeft: () => null,
        headerRight: ({ color, size = 26 }) => (
          <MaterialCommunityIcons
            name="account"
            color={color}
            style={{ marginRight: 20 }}
            size={size}
            onPress={() => openDrawer()}
          />
        ),
        drawerPosition: "right",
      }}
    >
      <Drawer.Screen
        name="Main"
        component={MainScreen}
        options={{
          title: title,
          drawerItemStyle: { display: "none" },
        }}
      />
      <Drawer.Screen
        name="Califications"
        component={CalificationsScreen}
        options={{
          headerShown: true,
          headerLeft: ({ color, size = 26 }) => (
            <MaterialCommunityIcons
              name="arrow-left"
              color={color}
              style={{ marginLeft: 15 }}
              size={size}
              onPress={() => navigation.goBack()}
            />
          ),
          title: "Calificaciones",
        }}
      />
      <Drawer.Screen
        name="ChangePassword"
        component={ChangePassword}
        options={{
          headerShown: true,
          headerLeft: ({ color, size = 26 }) => (
            <MaterialCommunityIcons
              name="arrow-left"
              color={color}
              style={{ marginLeft: 15 }}
              size={size}
              onPress={() => navigation.goBack()}
            />
          ),
          title: "Cambiar contraseña",
        }}
      />
    </Drawer.Navigator>
  )
}

const mapStateToProps = state => ({
  title: state.navigationState.title,
})

const ConnectedApp = connect(mapStateToProps)(App)

const Root = () => {
  return (
    <Provider store={store}>
      <NavigationContainer>
        <ConnectedApp />
      </NavigationContainer>
    </Provider>
  )
}
export default Root

Main.js

const Main = ({ fetchUser, saveTabTitle }) => {
  useEffect(() => {
    fetchUser()
  }, [])

  const Tab = createMaterialBottomTabNavigator()

  const handleTabPress = title => {
    saveTabTitle(title)
  }

  return (
    <Tab.Navigator
      initialRouteName="Course"
      labeled={false}
      activeColor="#f0edf6"
      inactiveColor="#3e2465"
    >
      <Tab.Screen
        name="Course"
        component={CoursesScreen}
        listeners={{
          tabPress: () => {
            handleTabPress("Cursos")
          },
        }}
        options={{
          tabBarIcon: ({ color, size = 26 }) => (
            <MaterialCommunityIcons name="school" color={color} size={size} />
          ),
        }}
      />
      <Tab.Screen
        name="Calendar"
        component={CalendarScreen}
        listeners={{
          tabPress: () => {
            handleTabPress("Calendario")
          },
        }}
        options={{
          tabBarIcon: ({ color, size = 26 }) => (
            <MaterialCommunityIcons name="calendar" color={color} size={size} />
          ),
        }}
      />
    </Tab.Navigator>
  )
}

const mapStateToProps = store => ({
  currentUser: store.userState.currentUser,
})

const mapDispatchToProps = dispatch =>
  bindActionCreators({ fetchUser, saveTabTitle }, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(Main)

解决方案

1. 将导航器创建移到组件外部

Native端对组件内部创建的导航器实例兼容性较差,登出卸载组件时容易出现引用失效问题。修改Main.js,把createMaterialBottomTabNavigator移到组件外部:

// 移到文件顶部,导入语句下方
const Tab = createMaterialBottomTabNavigator()

const Main = ({ fetchUser, saveTabTitle }) => {
  useEffect(() => {
    fetchUser()
  }, [])

  const handleTabPress = title => {
    saveTabTitle(title)
  }

  // 原返回部分不变
}

2. 修复子页面headerLeft的navigation引用

App.js中Drawer子页面的headerLeft使用了App组件的全局navigation变量,登出时该导航实例已被卸载,导致调用goBack()时出现undefined错误。修改为从headerLeft的props中获取当前页面的navigation:

// 以CalificationsScreen为例,其他子页面同理
<Drawer.Screen
  name="Califications"
  component={CalificationsScreen}
  options={{
    headerShown: true,
    headerLeft: ({ color, size = 26, navigation }) => (
      <MaterialCommunityIcons
        name="arrow-left"
        color={color}
        style={{ marginLeft: 15 }}
        size={size}
        onPress={() => navigation.goBack()}
      />
    ),
    title: "Calificaciones",
  }}
/>

3. 简化登出逻辑

检查LogOut组件的登出代码,确保只调用Firebase的signOut()方法,不要额外添加导航跳转逻辑——onAuthStateChanged会自动监听用户状态变化,更新loggedIn并触发App重新渲染为登录栈,避免手动导航导致的组件状态冲突。

4. 清理组件副作用(可选)

如果仍有问题,可在Main组件的useEffect中添加清理函数,避免登出后残留的异步操作触发回调:

useEffect(() => {
  const fetchUserPromise = fetchUser()
  return () => {
    // 如果fetchUser是异步操作,可在此标记组件已卸载,避免后续回调执行
  }
}, [fetchUser])

内容的提问来源于stack exchange,提问作者Pablo Martínez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 15:55:42