React Native页面Hook重复触发API调用问题排查与解决
解决React Native页面重复调用API及Hook重复执行问题
问题根源
useCheckVersionHook中,当apiVersion更新时会执行异步逻辑,若调用setNewVersion更新状态,会触发组件重渲染;useGetGrandPrix虽依赖空数组,但因未做防重复请求处理,加上组件多次重渲染的连锁反应,导致API重复调用;useCheckVersion内AsyncStorage.getItem的回调写法错误,可能引发逻辑异常,导致不必要的状态更新。
解决方案
1. 给useGetGrandPrix添加防重复请求逻辑
通过useRef标记请求状态,确保API仅调用一次:
import { useState, useEffect, useRef } from 'react'; import { getToken } from '../../../services/common/auth/storage'; import { getGranPrix } from '../../../services/common/api/circuits'; export const useGetGrandPrix = () => { const [data, setData] = useState({}); const [apiVersion, setApiVersion] = useState(''); const [loading, setLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(''); const hasFetched = useRef(false); // 标记是否已完成请求 useEffect(() => { const getData = async () => { if (hasFetched.current) return; // 已请求过则直接返回 hasFetched.current = true; try { const token = await getToken(); const response = await getGranPrix(token); if (!response.ok) { setErrorMessage('Could not retrieve data.'); } else { setData(response.data.list); setApiVersion(response.data.version); } } catch (err) { setErrorMessage('Could not retrieve data.'); } finally { setLoading(false); } }; getData(); }, []); return { data, apiVersion, loading, errorMessage }; };
2. 优化useCheckVersion逻辑
- 修复
AsyncStorage的错误用法,移除多余回调; - 仅在状态实际变化时更新,避免无意义重渲染:
import { useState, useEffect } from 'react'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { APP } from '../../../utils/constants/app'; export const useCheckVersion = (apiVersion) => { const [isNewVersion, setNewVersion] = useState(false); useEffect(() => { const getVersion = async () => { const checkVersion = () => { if (apiVersion === APP.VERSION) return false; return apiVersion > APP.VERSION; }; const versionNeedUpdate = checkVersion(apiVersion); let versionPrompt = { dateLastPrompt: null, dateNextPrompt: null, }; const currentWeekday = new Date().getUTCDay(); const currentPromptUTC = new Date().toISOString(); const nextPromptUTC = new Date(currentPromptUTC); nextPromptUTC.setDate(nextPromptUTC.getDate() + 7); nextPromptUTC.setHours(0, 0, 0, 0); if (!versionNeedUpdate) { const storageItem = await AsyncStorage.getItem('versionPrompt'); if (storageItem) { await AsyncStorage.removeItem('versionPrompt'); } // 若当前不需要更新且状态为true,才更新状态 if (isNewVersion) { setNewVersion(false); } return; } // 需要更新的逻辑 const storageItemString = await AsyncStorage.getItem('versionPrompt'); const storageItem = storageItemString ? JSON.parse(storageItemString) : null; if (!storageItem) { versionPrompt = { dateLastPrompt: currentPromptUTC, dateNextPrompt: nextPromptUTC, }; await AsyncStorage.setItem('versionPrompt', JSON.stringify(versionPrompt)); if (!isNewVersion) { setNewVersion(true); } } else { const dateNextPrompt = new Date(storageItem.dateNextPrompt); const currentDate = new Date(); if (dateNextPrompt < currentDate && currentWeekday > 2) { versionPrompt = { dateLastPrompt: currentPromptUTC, dateNextPrompt: nextPromptUTC, }; await AsyncStorage.setItem('versionPrompt', JSON.stringify(versionPrompt)); if (!isNewVersion) { setNewVersion(true); } } else { // 不需要提示更新时,若状态为true则重置 if (isNewVersion) { setNewVersion(false); } } } }; getVersion(); }, [apiVersion, isNewVersion]); return { isNewVersion }; };
3. 修复Screen组件的导航参数问题
确保navigation从props中解构,避免报错引发的异常渲染:
import React from 'react'; import { FlatList, Text } from 'react-native'; import { useGetGrandPrix } from '../hooks/api'; import { useCheckVersion } from '../hooks/system'; import GrandPrix from './GrandPrix'; // 需确保自定义组件已导入 function GrandPrixScreen(props) { const { navigation } = props; // 解构navigation const { data, apiVersion, loading, errorMessage } = useGetGrandPrix(); const { isNewVersion } = useCheckVersion(apiVersion); if (loading) { return <Text>Loading...</Text>; } if (errorMessage) { return <Text>{errorMessage}</Text>; } if (isNewVersion) { return <Text>Please update app to latest version.</Text>; } return ( <FlatList data={data} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <GrandPrix name={item.name} length={item.length} onPress={() => navigation.navigate('GrandPrixDetailsScreen', { item }) } /> )} /> ); } export default GrandPrixScreen;
4. 排除React严格模式影响(开发环境)
若处于开发环境,React严格模式会让useEffect执行两次(生产环境不会)。可检查根组件是否包裹<StrictMode>,若需避免开发环境的重复请求,可临时移除,但生产环境无需处理。
内容的提问来源于stack exchange,提问作者monoctis
相关产品推荐
相关产品推荐

