React Native:如何在TS文件的API异常中处理导航跳转?求更优方案
问题描述
我们使用Expo和@react-navigation/native-stack开发应用,要求用户必须接受最新服务条款才能使用。若用户登录期间条款发生变更,需要弹出提示并将其重定向至条款页面。
API调用位于独立的.ts文件中,无法自行处理导航。目前我们的方案是在根导航器中暴露一个函数供API文件调用,以此集中处理错误,避免在多个API调用位置分散处理逻辑。
理想的API调用逻辑如下:
// api.ts export const doSomething = async () => { const response = await axios.post( 'someUrl', { someData: 'someData' }, ).catch((err) => { if (err.status === 401) { showAlertToNavigateToTermsOfUse(); } }) }
当前实现方式是在根导航器中暴露一个对象:
export const exposedObject = {showAlertToNavigateToTermsOfUse: () => {}} export default function RootNavigator() { const navigator = useNavigation<any>(); exposedObject.showAlertToNavigateToTermsOfUse = () => { Alert.alert('服务条款变更', '服务条款已更新,请重新接受后继续使用') navigator.navigate(ScreenNames.TERMS_OF_USE_SCREEN); }; // ... 导航器其余代码 }
该方案可行但不够优雅,想知道React Native有没有更优的实现方式?
更优实现方案
方案一:React Context封装导航服务
通过Context创建全局可用的导航服务,既保持API层与组件的解耦,又符合React的设计范式。
- 创建导航Context与自定义Hook
// NavigationService.tsx import { createContext, useContext, useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { ScreenNames } from './ScreenNames'; import { Alert } from 'react-native'; type NavigationServiceType = { showAlertAndNavigateToTerms: () => void; }; const NavigationServiceContext = createContext<NavigationServiceType | undefined>(undefined); export const NavigationServiceProvider = ({ children }: { children: React.ReactNode }) => { const navigation = useNavigation<NativeStackNavigationProp<any>>(); const showAlertAndNavigateToTerms = () => { Alert.alert('服务条款变更', '服务条款已更新,请重新接受后继续使用'); navigation.navigate(ScreenNames.TERMS_OF_USE_SCREEN); }; return ( <NavigationServiceContext.Provider value={{ showAlertAndNavigateToTerms }}> {children} </NavigationServiceContext.Provider> ); }; export const useNavigationService = () => { const context = useContext(NavigationServiceContext); if (!context) { throw new Error('useNavigationService必须在NavigationServiceProvider内部使用'); } return context; };
- 在根组件中包裹Provider
// App.tsx import { NavigationServiceProvider } from './NavigationService'; import RootNavigator from './RootNavigator'; export default function App() { return ( <NavigationServiceProvider> <RootNavigator /> </NavigationServiceProvider> ); }
- 封装API为Hook供组件调用
// useApi.ts import { useNavigationService } from './NavigationService'; import axios from 'axios'; export const useApi = () => { const { showAlertAndNavigateToTerms } = useNavigationService(); const doSomething = async () => { try { const response = await axios.post('someUrl', { someData: 'someData' }); return response; } catch (err) { if (err.response?.status === 401) { showAlertAndNavigateToTerms(); } throw err; } }; return { doSomething }; };
组件内使用示例:
// SomeComponent.tsx import { useApi } from './useApi'; const SomeComponent = () => { const { doSomething } = useApi(); const handlePress = async () => { await doSomething(); }; return <Button title="执行操作" onPress={handlePress} />; };
方案二:事件总线解耦API与导航
通过事件发射器实现完全解耦,API层仅负责触发事件,导航层监听事件并处理逻辑。
- 创建事件总线
// EventBus.ts import { EventEmitter } from 'events'; const eventBus = new EventEmitter(); export const EVENT_TERMS_UPDATED = 'TERMS_UPDATED'; export default eventBus;
- 在根导航器中监听事件
// RootNavigator.tsx import { useEffect } from 'react'; import { useNavigation } from '@react-navigation/native'; import eventBus, { EVENT_TERMS_UPDATED } from './EventBus'; import { Alert } from 'react-native'; import { ScreenNames } from './ScreenNames'; export default function RootNavigator() { const navigator = useNavigation<any>(); useEffect(() => { const handleTermsUpdated = () => { Alert.alert('服务条款变更', '服务条款已更新,请重新接受后继续使用'); navigator.navigate(ScreenNames.TERMS_OF_USE_SCREEN); }; eventBus.on(EVENT_TERMS_UPDATED, handleTermsUpdated); return () => { eventBus.off(EVENT_TERMS_UPDATED, handleTermsUpdated); }; }, [navigator]); // ... 导航器其余代码 }
- 在API文件中触发事件
// api.ts import axios from 'axios'; import eventBus, { EVENT_TERMS_UPDATED } from './EventBus'; export const doSomething = async () => { try { const response = await axios.post('someUrl', { someData: 'someData' }); return response; } catch (err) { if (err.response?.status === 401) { eventBus.emit(EVENT_TERMS_UPDATED); } throw err; } };
方案三:Axios拦截器+全局导航引用
适合需要全局统一处理API错误的场景,代码简洁且无需分散处理逻辑。
- 创建全局导航引用
// NavigationRef.ts import type { NavigationContainerRef } from '@react-navigation/native'; export let navigationRef: NavigationContainerRef<any>; export const setNavigationRef = (ref: NavigationContainerRef<any>) => { navigationRef = ref; };
- 在根组件中设置引用
// App.tsx import { NavigationContainer } from '@react-navigation/native'; import { setNavigationRef } from './NavigationRef'; import RootNavigator from './RootNavigator'; export default function App() { return ( <NavigationContainer ref={setNavigationRef}> <RootNavigator /> </NavigationContainer> ); }
- 配置Axios全局拦截器
// api.ts import axios from 'axios'; import { navigationRef } from './NavigationRef'; import { Alert } from 'react-native'; import { ScreenNames } from './ScreenNames'; const api = axios.create({ baseURL: 'your-base-url', }); api.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { Alert.alert('服务条款变更', '服务条款已更新,请重新接受后继续使用'); navigationRef.navigate(ScreenNames.TERMS_OF_USE_SCREEN); } return Promise.reject(error); } ); export const doSomething = async () => { const response = await api.post('someUrl', { someData: 'someData' }); return response; };
总结
- 若偏好React组件范式,**方案一(Context+Hook)**是最贴合React设计理念的选择;
- 若需要完全解耦API与导航层,**方案二(事件总线)**扩展性更强;
- 若追求简洁的全局错误处理,**方案三(Axios拦截器+全局导航引用)**更高效。
内容的提问来源于stack exchange,提问作者Scott Fletcher
相关产品推荐
相关产品推荐

