渲染比上一次渲染更多的钩子

Renderer more hooks than during the previous render

我正在做一个带有 bare expo 和 React native 的项目。我已经在我的项目中实施了 firebase。登录和注册工作正常。我已经创建了一个身份验证检查,以在用户登录时将其发送到另一个页面。虽然我在尝试将用户发送到另一个页面时收到以下错误:

这是我的 App.tsx:

import React, { useEffect, useState } from 'react';
import { StatusBar } from 'expo-status-bar';
import { ThemeProvider } from 'styled-components';
import {
    useFonts,
    Poppins_400Regular,
    Poppins_500Medium,
    Poppins_700Bold
} from '@expo-google-fonts/poppins';
import AppLoading from 'expo-app-loading';
import theme from './src/global/styles/theme';
import { NavigationContainer } from '@react-navigation/native';
import { AppRoutes } from './src/routes/app.routes';
import 'intl';
import 'intl/locale-data/jsonp/pt-BR';
import { SignIn } from './src/screens/SignIn';
import auth, { FirebaseAuthTypes } from '@react-native-firebase/auth';

export default function App() {

    const [ fontsLoaded ] = useFonts({
        Poppins_400Regular,
        Poppins_500Medium,
        Poppins_700Bold
    });
    const [user, setUser] = useState<FirebaseAuthTypes.User | null>(null);

    if (!fontsLoaded) {
        return <AppLoading />;
    }

    useEffect(() => {
        const subscriber = auth().onAuthStateChanged(setUser);
        return subscriber;
    }, []);

  return (
        <ThemeProvider theme={theme}>
            <StatusBar style="light"/>
            <NavigationContainer>
                { user ? <AppRoutes /> : <SignIn /> }
            </NavigationContainer>                      
        </ThemeProvider>
  );
}

这是路线代码:

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Dashboard } from '../screens/Dashboard';
import { Register } from '../screens/Register';
import { useTheme } from 'styled-components';
import { Platform } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { Summary } from '../screens/Summary';
 
const { Navigator, Screen } = createBottomTabNavigator();

export function AppRoutes() {
    const theme = useTheme();

    return (
        <Navigator
            screenOptions={{
                headerShown: false,
                tabBarActiveTintColor: theme.colors.orange,
                tabBarInactiveTintColor: theme.colors.texts,
                tabBarLabelPosition: 'beside-icon',
                tabBarStyle: {
                    height: 88,
                    paddingVertical: Platform.OS === 'ios' ? 20 : 0
                }
            }}
        >
            <Screen 
                key="Listing"
                name="Listing"
                component={Dashboard}
                options={{
                    tabBarIcon: (({ size, color }) => 
                        <MaterialIcons 
                            name="format-list-bulleted"
                            size={size}
                            color={color}
                        /> 
                    )
                }}
            />
            <Screen
                key="Register"
                name="Register"
                component={Register}
                options={{
                    tabBarIcon: (({ size, color }) => 
                        <MaterialIcons 
                            name="attach-money"
                            size={size}
                            color={color}
                        /> 
                    )
                }}
            />
            <Screen 
                key="Summary"
                name="Summary"
                component={Summary}
                options={{
                    tabBarIcon: (({ size, color }) => 
                        <MaterialIcons 
                            name="pie-chart"
                            size={size}
                            color={color}
                        /> 
                    )
                }}
            />
        </Navigator>
    );
}

您的问题来自此版块:

if (!fontsLoaded) {
    return <AppLoading />;
}

useEffect(() => {
    const subscriber = auth().onAuthStateChanged(setUser);
    return subscriber;
}, []);

您有条件地在此处添加一个挂钩,因为如果 fontsLoaded 评估为 false,您 return 渲染函数更早并且 useEffect 挂钩永远不会 运行第一个渲染。但是,在随后的尝试中,可能会加载字体,这会导致呈现不同数量的挂钩:因此会出现错误。

基于react自己的"rules of hooks" guide,强调我自己的:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

要解决此问题,只需确保在任何 return 路径之前定义所有挂钩。在您的情况下,这是通过交换 fontsLoaded 检查来完成的:

// Define all hooks before any return paths!
useEffect(() => {
    const subscriber = auth().onAuthStateChanged(setUser);
    return subscriber;
}, []);


if (!fontsLoaded) {
    return <AppLoading />;
}

return (
    <ThemeProvider theme={theme}>
        <StatusBar style="light"/>
        <NavigationContainer>
            { user ? <AppRoutes /> : <SignIn /> }
        </NavigationContainer>                      
    </ThemeProvider>
);

当然有时很难捕捉到这样的错误,尤其是在 heavy/complicated 组件中。如果您使用的是 eslint,则有一个名为 eslint-plugin-react-hook 的 eslint 插件会导致 eslint 在发生这种情况时抛出 warning/error。