React Hooks调用顺序错误求助:ScreenA组件报错修复咨询
解决React Hooks调用顺序错误警告
报错信息
ERROR Warning: React has detected a change in the order of Hooks called by ScreenA. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
当前代码功能可正常运行,但触发了Hooks调用顺序错误的警告,需修复后再扩展功能。
问题代码
import React, { useCallback, useEffect, useState } from "react"; import { View, Text, StyleSheet, Pressable } from "react-native"; import { useNavigation } from '@react-navigation/native'; import { DancingScript_400Regular } from "@expo-google-fonts/dancing-script"; import * as SplashScreen from 'expo-splash-screen'; import * as Font from 'expo-font'; export default function ScreenA({ route }) { const [appIsReady, setAppIsReady] = useState(false); useEffect(() => { async function prepare() { try { // Keep the splash screen visible while we fetch resources await SplashScreen.preventAutoHideAsync(); // Pre-load fonts, make any API calls you need to do here await Font.loadAsync({ DancingScript_400Regular }); // Artificially delay for two seconds to simulate a slow loading // experience. Please remove this if you copy and paste the code! await new Promise(resolve => setTimeout(resolve, 2000)); } catch (e) { console.warn(e); } finally { // Tell the application to render setAppIsReady(true); } } prepare(); }, []); const onLayoutRootView = useCallback(async () => { if (appIsReady) { // This tells the splash screen to hide immediately! If we call this after // `setAppIsReady`, then we may see a blank screen while the app is // loading its initial state and rendering its first pixels. So instead, // we hide the splash screen once we know the root view has already // performed layout. await SplashScreen.hideAsync(); } }, [appIsReady]); if (!appIsReady) { return null; } const navigation = useNavigation(); const onPressHandler = () => { // navigation.navigate('Screen_B', { itemName: 'Item from Screen A', itemID: 12 }); } return ( <View style={styles.body} onLayout={onLayoutRootView}> <Text style={styles.text}> Screen A </Text> <Pressable onPress={onPressHandler} style={({ pressed }) => ({ backgroundColor: pressed ? '#ddd' : '#0f0' })} > <Text style={styles.text}> Go To Screen B </Text> </Pressable> <Text style={styles.text}>{route.params?.Message}</Text> </View> ) } const styles = StyleSheet.create({ body: { flex: 1, justifyContent: 'center', alignItems: 'center', }, text: { fontSize: 40, margin: 10, fontFamily: 'DancingScript_400Regular' } })
解决方案
问题根源是useNavigation Hook被放在了条件判断语句之后。根据React Hooks规则,所有Hooks必须在组件的顶层作用域调用,不能放在条件、循环、嵌套函数或返回语句之后,确保组件每次渲染时Hooks的调用顺序完全一致。
修复方法:将const navigation = useNavigation();移到组件顶层,也就是if (!appIsReady) { return null; }之前。
修复后的代码
import React, { useCallback, useEffect, useState } from "react"; import { View, Text, StyleSheet, Pressable } from "react-native"; import { useNavigation } from '@react-navigation/native'; import { DancingScript_400Regular } from "@expo-google-fonts/dancing-script"; import * as SplashScreen from 'expo-splash-screen'; import * as Font from 'expo-font'; export default function ScreenA({ route }) { const [appIsReady, setAppIsReady] = useState(false); // 移到组件顶层 const navigation = useNavigation(); useEffect(() => { async function prepare() { try { // Keep the splash screen visible while we fetch resources await SplashScreen.preventAutoHideAsync(); // Pre-load fonts, make any API calls you need to do here await Font.loadAsync({ DancingScript_400Regular }); // Artificially delay for two seconds to simulate a slow loading // experience. Please remove this if you copy and paste the code! await new Promise(resolve => setTimeout(resolve, 2000)); } catch (e) { console.warn(e); } finally { // Tell the application to render setAppIsReady(true); } } prepare(); }, []); const onLayoutRootView = useCallback(async () => { if (appIsReady) { // This tells the splash screen to hide immediately! If we call this after // `setAppIsReady`, then we may see a blank screen while the app is // loading its initial state and rendering its first pixels. So instead, // we hide the splash screen once we know the root view has already // performed layout. await SplashScreen.hideAsync(); } }, [appIsReady]); if (!appIsReady) { return null; } const onPressHandler = () => { // navigation.navigate('Screen_B', { itemName: 'Item from Screen A', itemID: 12 }); } return ( <View style={styles.body} onLayout={onLayoutRootView}> <Text style={styles.text}> Screen A </Text> <Pressable onPress={onPressHandler} style={({ pressed }) => ({ backgroundColor: pressed ? '#ddd' : '#0f0' })} > <Text style={styles.text}> Go To Screen B </Text> </Pressable> <Text style={styles.text}>{route.params?.Message}</Text> </View> ) } const styles = StyleSheet.create({ body: { flex: 1, justifyContent: 'center', alignItems: 'center', }, text: { fontSize: 40, margin: 10, fontFamily: 'DancingScript_400Regular' } })
内容的提问来源于stack exchange,提问作者Jógvan Reinert Samuelsen
相关产品推荐
相关产品推荐

