运行环境
React Navigation 6.x
React Native >= 0.63.0
Node > 12
代码参考
npm地址
github地址:lib文件夹为库项目、demo文件夹为示例
React Navigation介绍
React Navigation为React Native提供的路由管理库,利用该库可以很好的管理页面跳转,及导航栏的配置。利用该库可以实现普通的页面跳转,但是在涉及拦截器使用的时候,实现起来就比较麻烦。我们举例说明一下,假如我们程序有如下几个页面
|-- pages
|-- login.js 登录页面
|-- record.js 我的订单页面
|-- buy.js 购买页面
|-- home.js 首页
我的订单跟购买页面需要依赖用户登录成功才能进入,那么我们常规的操作是通过标识来实现的,如下代码所示
pages/home.js
import React from 'react';
import { Button } from 'react-native';
class Home extends React.Component{
render(){
return (
<View style={styles.container}>
<Button title="我的订单" onPress={() => this.props.navigation.navigate('login'), {toPage: "/record"}} />
<Button title="购买" onPress={() => this.props.navigation.navigate('login'), {toPage: "/buy"}} />
</View>
);
}
}
如上所示,通过传递toPage来标识登录成功后,跳转的页面,如下修改登录页面,pages/login.js
import React from 'react';
import { Button } from 'react-native';
class Login extends React.Component{
login(){
...扒拉扒拉一顿登录操作
const { toPage } = this.props.route.params;
this.props.navigation.navigate(toPage);
}
render(){
return (
<View style={styles.container}>
<Button title="我的订单" onPress={this.login} />
</View>
);
}
}
从以上可以看出,非常繁琐,而且代码非常耦合,有没有更好的解决方案,答案是有的,也就是拦截器的概念,通过配置拦截器让页面只需关系自己业务功能就行了。
如何设计拦截器
先看下React Navigation是怎么配置路由表的,app.js如下所示:
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { Home } from './pages/home';
import { Login } from './pages/login';
import { Record } from './pages/record';
import { Buy } from './pages/buy';
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="/home" component={Home} />
<Stack.Screen name="/login" component={Login} />
<Stack.Screen name="/record" component={Record} />
<Stack.Screen name="/buy" component={Buy} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
如何设置路由表
首先,我们需要有个路由表,然后根据路由表解析成React Navigation认识的形式,也就是上面的代码。新增路由表route-config.js,代码如下
import { Home } from './pages/home';
import { Login } from './pages/login';
export default {
"/home":{
component: Home
},
"/login":{
component: Login
}
}
从上面的路由可以看到路由的路径是以 ‘/’ 开头的,做过Web应该比较熟悉,对以后做三端(Android、iOS、Web)重构也是适配的。
然后解析成React Navigation认识的格式:
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
import routeConfig from './route-config'
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
{
Object.keys(routeConfig).map((key) => {
const item = routes[key];
return <Stack.Screen name={key} component={item.component} key={key}/>
}
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
如何配置拦截器
路由表已经有了,接着思考如何加上拦截器,考虑到一个页面有多个拦截器的可能性,所以拦截器应该是一个数组,拦截器在一开始就能确定,所以修改route-config.js,如下所示
import { Home } from './pages/home';
import { Login } from './pages/login';
import { Record } from './pages/record';//我的订单
import { LoginInterceptor } from './interceptors/login-interceptor';//登录拦截器
export default {
"/home":{
component: Home
},
"/record":{
interceptors:[
{
clazz: LoginInterceptor
}
]
component: Record
}
}
拦截器写好,以上逻辑为进入我的订单路由 /record 会先检验登录拦截器,也就是判断用户有没有登录,没有登录的话,会先在登录拦截器里面跳转到登录页面。登录拦截器代码如下:
class LoginInterceptor {
intercept() {
if(已登录){
跳转到下一个拦截器或到目标页
}else {
跳转到登录页面
}
}
}
intercept为拦截函数
如何才能执行拦截器
这一步,首先我们得先用个管理类,来管理包裹React Navigation的跳转方法,等拦截器执行结束在执行跳转到目标页面。所以新增lib/router.js。
export class Router {
// name为路由如/record,params为参数
push(name, params){}
}
Router 需要拿到React Navigation的ref,才能跳转,修改app.js。
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import routeConfig from './route-config'
import { navigationRef } from './router';
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer ref={navigationRef}>
<Stack.Navigator>
{
Object.keys(routeConfig).map((key) => {
const item = routes[key];
return <Stack.Screen name={key} component={item.component} key={key}/>
}
</Stack.Navigator>
</NavigationContainer>
);
}
修改lib/router.js,涉及拦截过滤器模式
import { createNavigationContainerRef } from '@react-navigation/native';
import { StackActions, CommonActions } from '@react-navigation/native';
import { FilterManager } from './filter-manager';
import routeResolve from './route-resolve';
import routeConfig from './route-config'
export class Router {
constructor() {
this.filterManager = {};
this.startLen = 0;
}
//跳转函数
push(name, params){
// 声明目标函数
function targetFun() {
const pushAction = StackActions.push(name, params);
navigationRef.dispatch(pushAction);
}
this.execute(name, targetFun);
}
execute(name, targetFun) {
let route = routeConfig[name];
if (route) {
const interceptors = route.interceptors;
if (interceptors) {
let interceptorClazzs = [];
interceptors.forEach(element => {
interceptorClazzs.push(element["clazz"]);
});
const state = navigationRef.getState();
this.startLen = state ? state.routes.length : 1;
// 过滤管理器
this.filterManager = new FilterManager(interceptorClazzs, targetFun);
this.filterManager.execute();
} else {
targetFun();
}
}
}
/**
* 执行下一个拦截器
*/
interceptNext() {
this.clearStack();
this.filterManager.execute();
}
/**
* 清栈
*/
clearStack() {
navigationRef.dispatch(state => {
const routes = state.routes.filter((r, index) => {
return index < this.startLen;
});
return CommonActions.reset({
...state,
routes,
index: routes.length - 1,
});
});
}
}
export const navigationRef = createNavigationContainerRef();
export let router = new Router();
新增过滤管理器 lib/filter-manager.js
/**
* 拦截过滤管理器
*/
export class FilterManager {
constructor(interceptorClazzs, targetFun) {
this.index = 0;
this.targetFun = targetFun;
this.interceptorClazzs = interceptorClazzs;
}
/**
* 执行拦截器
*/
execute(){
if(this.index == this.interceptorClazzs.length){
// 执行目标函数
this.targetFun();
}else{
// 获取下一个拦截器
let interceptor = new this.interceptorClazzs[this.index]();
this.index++;
interceptor.intercept();
}
}
}
至此,拦截器的设计就完成了,可以愉快的调用了。
使用router跳转
修改Home页面的跳转,对应的就会调起登录拦截器
import React from 'react';
import { Button } from 'react-native';
import { router } from './lib/router';
class Home extends React.Component{
render(){
return (
<View style={styles.container}>
<Button title="我的订单" onPress={() => router.push('/record')} />
<Button title="购买" onPress={() => router.push('/buy')} />
</View>
);
}
}