React Native嵌套导航导致组件初始化时API被调用两次的解决方法
解决React Native嵌套导航中组件重复渲染导致API重复调用的问题
问题原因
你的代码中DiscoverScreen同时被注册为TabNavigator的直接页面和StackNavigator的子页面,这会生成两个完全独立的组件实例。无论你是切换到Discover标签,还是从Home栈导航进入Discover,两个实例都会各自执行useEffect,最终导致API被调用两次。
解决方案
方案一:统一导航结构(推荐)
从根源上避免组件重复注册,调整导航层级,让DiscoverScreen只属于一个导航容器。可以给每个Tab都配置独立的StackNavigator:
// 定义Home标签对应的栈导航 const HomeStack = () => { return ( <Stack.Navigator initialRouteName="HomeScreen"> <Stack.Screen name="HomeScreen" component={HomeScreen} options={{ headerShown: false }} /> <Stack.Screen name="Planty" component={PlantyScreen} options={{ headerShown: false }} /> <Stack.Screen name="Specific" component={SpecificPlantScreen} options={{ headerShown: false }} /> </Stack.Navigator> ); }; // 定义Discover标签对应的栈导航 const DiscoverStack = () => { return ( <Stack.Navigator initialRouteName="Discover"> <Stack.Screen name="Discover" component={DiscoverScreen} options={{ headerShown: false }} /> {/* 可添加Discover页面需要跳转的其他页面 */} </Stack.Navigator> ); }; // 定义MyGarden标签对应的栈导航 const MyGardenStack = () => { return ( <Stack.Navigator initialRouteName="MyGarden"> <Stack.Screen name="MyGarden" component={MyGardenScreen} options={{ headerShown: false }} /> </Stack.Navigator> ); }; // 外层Tab导航 const TabNavigator = () => { return ( <Tab.Navigator initialRouteName="Home" screenOptions={() => ({ tabBarStyle: { height: 80, backgroundColor: "#232516", paddingBottom: 5, }, headerShown: false, tabBarActiveTintColor: "white", tabBarInactiveTintColor: "gray", })} > <Tab.Screen name="Home" component={HomeStack} options={{ tabBarIcon: ({ focused }) => ( <FontAwesome style={focused ? styles.focusedNavButtonBackground : {}} name="home" size={35} color={focused ? "black" : "white"} /> ), }} /> <Tab.Screen name="Discover" component={DiscoverStack} options={{ tabBarIcon: ({ focused }) => ( <FontAwesome style={focused ? styles.focusedNavButtonBackground : {}} name="search" size={35} color={focused ? "black" : "white"} /> ), }} /> <Tab.Screen name="MyGarden" component={MyGardenStack} options={{ tabBarIcon: ({ focused }) => ( <FontAwesome6 style={focused ? styles.focusedNavButtonBackground : {}} name="sun-plant-wilt" size={35} color={focused ? "black" : "white"} /> ), }} /> </Tab.Navigator> ); };
如果需要从Home页面跳转到Discover,直接调用navigation.navigate('Discover')即可,这样会切换到Tab的Discover标签,使用同一个组件实例。
方案二:在API层添加缓存逻辑
如果暂时不想调整导航结构,可以在usePlants Hook中添加请求缓存,避免相同参数的重复调用:
const usePlants = () => { const [plants, setPlants] = useState([]); const [errorMessage, setErrorMessage] = useState(''); const [page, setPage] = useState(1); const lastRequestedPage = useRef(null); const getPlants = async (currentPage) => { // 跳过重复的page请求 if (lastRequestedPage.current === currentPage) return; try { lastRequestedPage.current = currentPage; // 替换为你的实际API地址 const response = await fetch(`your-api-endpoint?page=${currentPage}`); const data = await response.json(); setPlants(prev => [...prev, ...data]); setErrorMessage(''); } catch (err) { setErrorMessage('获取植物列表失败'); // 请求失败时重置缓存标记,允许重新请求 lastRequestedPage.current = null; } }; const incrementPageNumber = () => { setPage(prev => prev + 1); }; useEffect(() => { getPlants(page); }, [page]); return [getPlants, incrementPageNumber, page, plants, errorMessage]; };
这样即使存在两个DiscoverScreen实例,当它们请求相同page的数据时,只会触发一次API调用。
方案三:统一页面跳转入口
如果希望从Home栈跳转到Discover时直接切换到Tab标签,而非在栈内创建新实例,修改Home页面的跳转逻辑:
// 原栈内跳转逻辑(会创建新实例) // navigation.navigate('Discover'); // 修改为直接跳转到Tab的Discover标签 navigation.navigate('Discover');
这样所有进入Discover的操作都会复用Tab中的组件实例,避免重复渲染。
内容的提问来源于stack exchange,提问作者Marius Stoica
相关产品推荐
相关产品推荐

