React Native路由系统重构:基于react-navigation的封装实践
在React Native项目中,使用react-navigation作为路由解决方案时,官方示例通常较为基础。随着页面数量增加,App.tsx主文件需要重复添加大量RootStack.Screen组件,且路由对象navigation需层层传递。虽然可以通过useDispatch和useNavigation等hooks获取,但为了提升代码可维护性和便于系统升级,我们封装了routers配置及MRouter管理类。本文将详细介绍路由系统的重构,涵盖路由配置、路由管理和页面使用三部分,文末提供完整代码示例。
路由配置 src/router.js
路由配置主要包括动画枚举、屏幕方向枚举、默认配置及路由对象定义。
动画与屏幕方向枚举
定义路由切换动画类型:
const animationType = {
default: 'default',
fade: 'fade',
fade_from_bottom: 'fade_from_bottom',
flip: 'flip',
none: 'none',
simple_push: 'simple_push',
slide_from_bottom: 'slide_from_bottom',
slide_from_right: 'slide_from_right',
slide_from_left: 'slide_from_left'
};
定义屏幕方向枚举:
const orientationType = {
default: 'default',
all: 'all',
portrait: 'portrait',
portrait_up: 'portrait_up',
portrait_down: 'portrait_down',
landscape: 'landscape',
landscape_left: 'landscape_left',
landscape_right: 'landscape_right'
};
默认路由配置
设置默认的导航选项:
const screenConfig: NativeStackNavigationOptions = {
headerShown: true,
headerShadowVisible: false,
headerTitleAlign: 'center',
headerBackTitle: '',
headerBackVisible: false,
headerBackTitleVisible: false,
headerTintColor: '#3D3F43',
animation: animationType.slide_from_right,
orientation: orientationType.portrait,
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0
},
headerTitleStyle: {
fontSize: 18,
fontWeight: '500',
color: '#3D3F43'
}
};
自定义标题与选项生成
创建自定义标题组件:
const getHeaderTitle = (text) => {
return (
<View style={{
height: 28,
marginBottom: 8,
justifyContent: 'center',
alignItems: 'center'
}}>
<UIText style={[{
fontSize: 18,
fontWeight: '500'
}, { color: '#3D3F43' }]}>{text}</UIText>
</View>
);
};
生成路由选项函数:
const getOptions = (param: NativeStackNavigationOptions) => {
const options: NativeStackNavigationOptions = {
...screenConfig,
...param,
headerLeft: () => <HeaderBackArrow />,
headerTitle: () => getHeaderTitle(param.title || '')
};
return options;
};
路由配置对象
定义路由映射,包含名称、选项和组件:
const routers = {
Login: { name: 'Login', options: getOptions({ animation: animationType.slide_from_bottom, headerShown: false }), component: Login },
Info: { name: 'Info', options: getOptions({}), component: Info },
Tabbar: { name: 'Tabbar', options: getOptions({ animation: animationType.slide_from_right }), component: Tabbar },
Agreement: { name: 'Agreement', options: getOptions({}), component: Agreement },
// 其他路由...
};
路由管理 src/framework/MRouter.ts
路由管理类提供全局导航控制方法。
设置路由引用
存储导航器引用:
let _navigator: any;
function setNavigator(navigatorRef: any) {
_navigator = navigatorRef;
}
核心导航方法
打开新页面,支持参数传递和是否开启新页面标志:
function open(name: string, params?: any, newPage?: boolean) {
let index = indexOfRouteByName(name);
let route = getRouteInfoByName(name);
let item: any = {
key: route?.key || name,
name: name,
path: name,
params: params || null
};
if (newPage || index === -1) item.key = name + generateRandom();
_navigator.dispatch(CommonActions.navigate(item));
}
重置路由栈到首页:
function home(name: string, params?: object) {
try {
let item = {
name: name,
path: name,
params: params,
key: name + generateRandom()
};
_navigator.dispatch(
CommonActions.reset({
index: 0,
routes: [item]
})
);
} catch (e) {
errorHandler.noRoute(name);
}
}
替换栈顶路由:
function replace(name: string, params?: any) {
let routers = _navigator.getRootState().routes || [];
let index = indexOfRouteByName(name);
if (name && index > -1) {
let item = {
index,
name: name,
path: name,
params: params,
key: name + generateRandom()
};
if (index > -1) routers = routers.slice(0, index);
routers.push(item);
_navigator.dispatch(
CommonActions.reset({
index: 0,
routes: routers
})
);
} else {
routers.pop();
let item = {
name: name,
params: params,
path: name,
key: name + generateRandom()
};
routers.push(item);
_navigator.dispatch(
CommonActions.reset({
index: 0,
routes: routers
})
);
}
}
路由返回,支持指定回退目标:
function back(name?: string, params?: any) {
let index = -1;
let route = null;
if (name) {
index = indexOfRouteByName(name);
route = getRouteInfoByName(name);
} else {
route = _navigator.getRootState().routes[_navigator.getRootState().routes.length - 1];
}
if (index > -1) {
open(route.name, params);
} else {
close();
}
params && _navigator.dispatch(
CommonActions.setParams({
params: params,
key: route && route.key,
source: route && route.key
})
);
}
关闭当前路由:
function close() {
if (_navigator.canGoBack()) {
_navigator.dispatch(CommonActions.goBack());
}
}
辅助函数
查找路由在栈中的位置:
function indexOfRouteByName(name: string) {
let routers = _navigator.getRootState().routes || [];
let index = -1;
let routeIndex = routers.length;
while (routeIndex > 0) {
routeIndex--;
let route = routers[routeIndex];
if (route.name === name) {
index = routeIndex;
break;
}
}
return index;
}
根据名称获取路由信息:
function getRouteInfoByName(name: string) {
let routers = _navigator.getRootState().routes || [];
let routeIndex = routers.length;
while (routeIndex) {
routeIndex--;
let route = routers[routeIndex];
if (route.name === name) {
return route;
}
}
return routers[routers.length];
}
App.tsx配置
在应用入口文件中集成路由系统。
导航容器设置
使用NavigationContainer包裹导航器:
function App() {
const navigationRef = useNavigationContainerRef();
return (
<SafeAreaProvider>
<NavigationContainer ref={navigationRef}>
<NavigatorFn navigationRef={navigationRef} />
</NavigationContainer>
</SafeAreaProvider>
);
}
MRouter初始化与路由动态生成
在组件中初始化MRouter并动态生成路由屏幕:
const NavigatorFn: React.FC<{ navigationRef: any }> = ({ navigationRef }) => {
useEffect(() => {
MRouter.setNavigator(navigationRef?.current);
}, []);
return (
<RootStack.Navigator
screenOptions={{
headerShown: false,
orientation: 'portrait'
}}>
{routers &&
Object.keys(routers).map((key, index) => {
return (
<RootStack.Screen
key={index}
name={routers[key].name}
options={routers[key].options}
getComponent={() => routers[key].component}
/>
);
})}
</RootStack.Navigator>
);
};
状态驱动路由跳转
根据应用状态(如登录状态)自动跳转:
useEffect(() => {
AsyncStorage.getItem('token')
.then(token => {
dispatch(setToken({ token }));
if (!token) {
MRouter.home(routers.Login.name);
}
})
.catch(err => {
MRouter.home(routers.Login.name);
});
}, [token]);
MRouter使用说明
MRouter可在任何页面直接调用,无需传递navigation对象,并兼容原navigation方法。
常用操作示例
打开页面:
MRouter.open('Login');
MRouter.open(routers.Login.name, { name: 'abc', key: 1 });
MRouter.open(routers.Login.name, { name: 'abc', key: 1 }, true); // 强制开启新页面
参数获取:
- 在类组件中通过
this.props.params获取。 - 在函数组件中使用
useRoute()hook获取。
重置路由栈:
MRouter.home(routers.Login.name);
MRouter.home(routers.Login.name, { key: 2, name: 'bcd' });
替换路由:
MRouter.replace(routers.Login.name);
MRouter.replace(routers.Login.name, { key: 2, name: 'bcd' });
返回操作:
MRouter.back(); // 返回上一页
MRouter.back(routers.Login.name); // 返回到指定页面
MRouter.back(routers.Login.name, { key: 2, name: 'bcd' });
代码示例摘要
关键代码文件概览:
MRouter.ts:封装完整的路由管理方法,包括打开、重置、替换、回退等。router.tsx:定义路由配置,包括枚举、选项和路由映射。App.tsx:应用入口,集成导航容器和状态管理。
总结
通过封装react-navigation,我们构建了一个可维护、易扩展的路由系统。MRouter类提供了统一的API,简化了页面跳转和状态管理,同时保持与原生导航方法的兼容性。这种重构提升了代码组织性,适用于中大型React Native项目。