[转]React Native 的路由架构分享以及配套神器推荐

搭建路由系统推荐使用 react-navigation 这个官方推荐的组件,该组件有三种导航(路由)系统:

  • 栈导航系统 StackNavigator
  • 标签导航系统 TabNavigator
  • 抽屉导航系统 DrawerNavigator

在我的应用中没有使用到抽屉导航系统,因此这里就不介绍这块相关的内容了(我也没看)。首先来看一下应用的基本结构。
PS.由于我比较懒,这里就不提供截图了,采用文字描述的形式,各位要是有不明白的地方可以问我。

应用基本结构

应用的基本结构如下:

闪屏和登陆

应用运行时,首先进入 Splash 闪屏,一段时间后跳转到登陆界面,登陆之后跳转到主页。在用户登陆时会有一个本地的持久化处理,如果用户登陆成功,那么下一次运行应用时,会直接跳转到主页。

主页

主页整体上是一个标签导航系统,整个标签导航系统分为四个标签:首页、数据、消息和我的。每个标签页中还拥有一些子路由,层次最多为三层,这个就不详细说了。

路由分层

根据前面的应用基本结构,可以将使用 APP 时的路由分为两层:从闪屏到登陆到主页为第一层,主页及其内部的路由为第二层。
分层之后,就可以搭建路由系统了。整体上采用栈式导航(StackNavigator),将闪屏页、登录页和主页作为栈式导航的子路由,主页内部采用标签式导航(TabNavigator)。

目录结构

我们采用如下的目录结构:

├─components
├─data
├─images
├─login
├─scene
├─tabs
│  ├─data_tab
│  │  └─dataComponents
│  ├─home_tab
│  ├─message_tab
│  └─Mine_tab
└─utils

下面解释下这些目录的作用:

  • components:存放公用组件
  • data:对接后端的 API,针对每个 tab 页面使用一个独立文件
  • images:项目中用到的图片
  • login:登陆界面
  • scene:闪屏(Splash)界面
  • tabs:存放主页中的界面,依据不同的 tab 进行子文件夹划分
  • utils:公共函数和配置等

路由配置

先来配置主页中的各个 Tab:

// 引入路由组件
import {
    StackNavigator,
    TabNavigator,
} from 'react-navigation'

import {
    Dimensions,
    ...
} from 'react-native'

// 获取屏幕宽度
const { width } = Dimensions.get('window');
// 闪屏界面
import SplashScreen from './scene/Splash'
// 登陆界面
import Login from './login/Login';
// 首页的一个界面
import HomeShowTab from './tabs/home_tab/HomeShowTab';
...
// 数据页的一个界面
import DataShowTab from './tabs/data_tab/DataShowTab';
...
// 消息页的一个界面
import MessageShowTab from './tabs/message_tab/MessageShowTab';
...
// 我的页的一个界面
import MineShowTab from  './tabs/Mine_tab/MineShowTab';
...

// 定义首页 Tab
const HomeTab=StackNavigator(
    {
        HomeShowTab: {
            screen: HomeShowTab,
        },
        ...
    },
    {
        headerMode: "screen"
    }
);

// 定义数据 Tab
const DataTab=StackNavigator(
    {
        DataShowTab: {
            screen: DataShowTab,
        },
        ...
    },
    {
        headerMode: "screen"
    }
);

// 定义消息 Tab
const MessageTab=StackNavigator(
    {
        FirstScreen: {
            screen: MessageShowTab,
            navigationOptions: {title: "消息"},
        },
        ...
    },
    {
        headerMode: "screen"
    }
);

// 定义我的 Tab
const MineTab=StackNavigator(
    {
        MineShowTab: {
            screen: MineShowTab,
        },
        ...
    },
    {
        headerMode: "screen"
    }
);

对于每一个 Tab 来说,它们内部应该使用栈式导航系统。
接下来,定义主页的标签导航:

// 底部菜单栏设置
const MainScreenNavigator = TabNavigator({
        HomeScreen: {
            screen: HomeTab,
            navigationOptions: {
                tabBarLabel: '首页',
                tabBarIcon: ({ tintColor,focused }) => {
                    return(
                        !focused?
                            
                        :
                            
                    );
                },
            },
        },
        DataScreen: {
            screen: DataTab,
            navigationOptions: {
                tabBarLabel:'数据',
                tabBarIcon: ({ tintColor,focused }) => {
                    return(
                        !focused?
                            
                        :
                            
                    );
                },
            }
        },
        MessageScreen: {
            screen: MessageTab,
            navigationOptions: {
                tabBarLabel:'消息',
                tabBarIcon: ({ tintColor,focused }) => {
                    return(
                        !focused?
                            
                        :
                            
                    );
                },
            }
        },
        MineScreen: {
            screen: MineTab,
            navigationOptions: {
                tabBarLabel:'我的',
                tabBarIcon: ({ tintColor,focused }) => {
                    return(
                        !focused?
                            
                        :
                            
                    );
                },
            }
        }
    },
    {
        initialRouteName:'HomeScreen',
        lazy:true,
        animationEnabled: false,
        tabBarPosition: 'bottom',
        swipeEnabled: false,
        tabBarOptions: {
            activeTintColor: '#42aff4',
            inactiveTintColor: '#999',
            showIcon: true,
            indicatorStyle: {
                height: 0
            },
            style: {
                backgroundColor: '#f0f3f5',
                height: 0.13066667 * width,
                justifyContent:"center",
            },
            labelStyle: {
                fontSize: 0.0293333 * width,
                marginTop:-0.008 * width,
            },
        }
    }
);

主页整体采用标签式导航,将每个标签的 screen 指向前面定义的各个 Tab。
接下来加入闪屏和登陆,构建整体的导航系统:

// 整体路由系统
const RootNavigator = StackNavigator({
    IndexScreen: {
        screen: MainScreenNavigator,
    },
    Splash:{screen: SplashScreen},
    Login:{screen: Login},

}, {
    // 默认显示界面为 Splash
    initialRouteName: "Splash",
    mode: 'card',
    headerMode: 'none',
});

然后导出我们配置的路由系统就可以了:

export default class MyAPP extends Component {
    render() {
        return (
            
                
            
        )
    }
}

至此,我们的导航系统就搭建好了,这是一个比较通用的系统,基本可以适用于一般的应用了。构建导航系统之后,剩下的工作就是在项目目录中添加各种各样的组件,以及使用 navigate 方法进行页面间的跳转了。
如果你是开发 IOS 应用,这样的架构就已经足够了,但如果你还要同时适配 Android(一般都会),就还需要做一点工作。

Android 的返回键问题

还记得吗?我们的应用是从 Splash 闪屏开始,根据用户是否登陆跳转到登陆界面或者主界面,在 IOS 下是没有问题的,但在 Android 下,由于返回键的存在,当跳转到登陆或者主界面时,还可以按返回键返回到 Splash 界面或者登陆界面,这显然是不合常理的。因此,在 Android 下,需要我们手动的对返回键进行处理。这就需要使用到 BackHandler 组件。
我们需要在两个界面对 BackHandler 组件进行处理:一个是登陆界面(阻止返回到 Splash 界面),另一个是在首页 Tab 的第一个界面(阻止返回到登陆界面)。在这两个界面中,我们需要对 BackHandler 进行事件监听,在用户连续点击两次返回键时退出应用,阻止默认的返回事件。
要完成这个功能,需要用到两个神器:react-navigation-is-focused-hoc 组件和 react-native-exit-app组件。

两个实用的组件

react-navigation-is-focused-hoc 是用来判断某个页面是否处于 Focus 状态。为什么需要这个组件呢?在 Android 上,当我们在某个界面对物理返回键进行事件监听时,会影响到所有界面的物理返回键功能,因此我们需要在跳转到其他界面之前移除对物理返回键的事件监听,在跳转回来时重新绑定事件监听。
跳转到其他页面时移除事件监听还好说,但是怎么对跳转回当前界面进行判断呢?因为有些跳转是通过 navigation.goBack() 进行的,并不会触发组件的生命周期,所以判断是相当麻烦的。react-navigation-is-focused-hoc 这个组件就是帮助我们来解决这个问题的。
PS.后续版本的 react-navigation 组件可能会开发相应的生命周期函数,请参考 #51。
react-native-exit-app 这个组件是干嘛的呢?这是因为我们在连续两次点击返回键时需要退出应用,如果使用 BackHandler 自带的 exitApp() 方法,无法完全结束应用的进程(参见#13483),导致下一次进入应用时返回键失效,因此我们需要使用 react-native-exit-app 这个组件实现应用的完全退出。

具体应用

下面是这两个组件的使用方法:
1.对跟路由组件的 onNavigationStateChange 事件进行监听:

import { updateFocus } from '@patwoz/react-navigation-is-focused-hoc'
...
export default class MyAPP extends Component {
    render() {
        return (
            
                 {
                        updateFocus(currentState)
                    }}
                />
            
        )
    }
}

2.对要监听物理返回键的界面进行处理:

import { withNavigationFocus } from '@patwoz/react-navigation-is-focused-hoc'
import RNExitApp from 'react-native-exit-app';

class HomeShowTab extends PureComponent {
    ...
    // 应用更新时绑定/解绑事件
    componentDidUpdate(prevProps) {
        const { isFocused } = this.props;
        if(isFocused){
            this.preventBackEvent();
        }else{
            this.removeBackEvent();
        }
    }      

    preventBackEventHander(){
        const time = +new Date();
        this.refs.toast.show("再按一次退出应用")
        if(!this.exitTimeFlag){
            this.exitTimeFlag = time;
            return true;
        }
        // 2500ms 内连续按键退出应用
        if(time - this.exitTimeFlag < 2500){
            this.removeBackEvent();
            this.timer = setTimeout(()=>{
                clearTimeout(this.timer);
                RNExitApp.exitApp();
            },200);
        }
        this.exitTimeFlag = time;

        return true;
    }

    // 绑定事件监听
    preventBackEvent(){
        BackHandler.addEventListener("hardwareBackPress",this.preventBackEventHander)
    }

    componentWillUnmount(){
        this.removeBackEvent();
    }

    // 移除事件监听
    removeBackEvent(){
        BackHandler.removeEventListener("hardwareBackPress",this.preventBackEventHander)
    }
    ...
}

然后,使用 withNavigationFocus 高阶组件进行一次包装即可:

export default withNavigationFocus(HomeShowTab)

可见,react-navigation-is-focused-hoc 的原理是对跟路由组件的 onNavigationStateChange 事件进行监听,当发生路由跳转时,将属性传递到对应的组件,以实现对界面是否处于 Focus 的判断。

安卓返回键问题的其他解决方案

针对安卓返回键的问题,我还看了其余的两个解决方案:

  • 集成 Redux,参见这里
  • 使用 getStateForAction 手动对路由栈进行管理

对于中小型的应用,没有必要使用 Redux,而对于使用 getStateForAction 手动对路由栈进行管理太过麻烦,需要考虑很多情况,我也没有研究透。
因此,对我个人而言,使用 react-navigation-is-focused-hocreact-native-exit-app 这两个组件是比较好的解决方案,这两个组件帮助我解决了安卓返回键这一大痛点,因此我将它们称为神器。

作者:黑黢黢
链接:https://www.jianshu.com/p/87ad53cefd06
來源:
著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

你可能感兴趣的:([转]React Native 的路由架构分享以及配套神器推荐)