React Native开发(七:如何实现绑定tabbar按钮新增权限判断和覆盖安卓默认返回事件)

本文介绍了一种在React Native应用中实现全局路由管理的方法,通过创建navigationService.js工具库,封装了react-navigation常用方法,实现了权限校验和路由跳转功能。

(一)前言

我们会遇到一种需求,在用户点击tabbar的时候,需要去判断当前用户是否有权限进入页面,那么我们需要在点击时候处理权限,然后跳转。基于这种需求,我们需要提供一个全局的navigationService.js方案提供全局跳转方案

(二) 版本环境

node 8.0+(v8.12.0)
npm 5.0+(v6.4.1)
java (v1.8.0_172)

"react-native": "0.57.1",
"react": "16.5.0",
"react-navigation": "^2.18.0",

(三) 具体实现

  1. 我们来实现全局工具库

在utils下新建navigationService.js 文件

import { NavigationActions, StackActions } from 'react-navigation';

let topNavigator;

// 递归查询当前routers
function getCurrRoute(object = {}) {
  const { index = 0, routes = [] } = object;
  const currRouteMap = routes[index];
  if (typeof currRouteMap.index !== 'undefined' && Array.isArray(currRouteMap.routes)) return getCurrRoute(currRouteMap);
  return {
    currRouteList: routes,
    index,
    currRouteMap,
  };
}

// 获取当前路由
export const getCurrRoutes = () => {
  if (!topNavigator) return {};
  const {
    state: {
      nav,
    } = {},
  } = topNavigator || {};
  if (!Object.keys(nav).length) return {};
  return getCurrRoute(nav);
};

export const registerTopNavigator = (navigatorRef) => {
  topNavigator = navigatorRef;
};

export const reset = (routeName, params) => {
  topNavigator.dispatch(StackActions.reset({
    index: 0,
    actions: [
      NavigationActions.navigate({
        routeName,
        params,
      }),
    ],
  }));
};

export const replace = ({
  key,
  newKey = null,
  routeName,
  params = null,
  action = null,
  immediate = null,
}) => {
  topNavigator.dispatch(StackActions.replace({
    key,
    newKey,
    routeName,
    params,
    action,
    immediate,
  }));
};

export const push = (routeName, params) => {
  topNavigator.dispatch(StackActions.push({
    routeName,
    params,
  }));
};

export const pop = (n) => {
  topNavigator.dispatch(StackActions.pop({
    n,
  }));
};

export const navigate = (routeName, params) => {
  topNavigator.dispatch(NavigationActions.navigate({
    routeName,
    params,
  }));
};

/**
 * Notice! behavior is not always same with props.navigation.goBack in View component
 * in the case of nested navigators, use props.navigation.goBack instead!
 */
export const goBack = (key = null) => {
  topNavigator.dispatch(NavigationActions.back({ key }));
};

这里我们单例对象设计,将topNavigator变量不做暴露,通过registerTopNavigator方法实现初始化。然后提供react-navigation常用的reset,replace,push,pop,navigate,goBack方法,然后通过递归提供getCurrRoutes方法,让外部能得到当前的路由。

  1. 我们现在常量文件定义顶层路由和使用createSwitchNavigator创建权限的路由配置
// constants/routers.js 代码如下

export default {
  // initial loading Stack
  authLoading: 'app.authLoading',
  // ...
}

// constants/index.js 代码如下
import routers from './routers';

// tabBar顶层路由
const tabBarTopRouterList = [
  routers.xx1, // 这里自己乱写的
  routers.xx2,
  routers.xx3,
  routers.xx4,
];

// 权限顶层路由
const authTopRouterList = [
  routers.signIn,
];

// 返回默认路由
const tabViewTopRouterName = routers.xx1;

export {
  routers,
  tabBarTopRouterList,
  authTopRouterList,
  tabViewTopRouterName,
};
  1. 然后我们在全局index.js 入口绑定ref和覆盖安卓默认返回事件
import Navigator from './pages';
import { Provider } from 'mobx-react';
import { BackHandler } from 'react-native';

// constants
import { theme, tabBarTopRouterList, authTopRouterList, tabViewTopRouterName } from './constants';

// models
import RootStore from './models';

// utils
import { goBack, registerTopNavigator, getCurrRoutes, navigate } from './utils/navigationService';

class App extends React.Component { 
  componentDidMount() {
    BackHandler.addEventListener('hardwareBackPress', this.onPressAndroidBack);
  }
   componentWillUnmount() {
    BackHandler.removeEventListener('hardwareBackPress', this.onPressAndroidBack);
  }
  onPressAndroidBack = () => {
    const {
      currRouteMap: {
        routeName,
        params = {},
      } = {},
    } = getCurrRoutes();
    if (!routeName) return true;
    // handle root stack
    if (tabBarTopRouterList.includes(routeName)) {
      BackHandler.exitApp();
      return false;
    }
    // handle auth stack 
    if (authTopRouterList.includes(routeName)) {
      navigate(params.fromRouteId || tabViewTopRouterName);
      return true;
    }
    // back to the previous page
    goBack();
    return true;
  };
  // ....
  render() { 
    return (
     <Provider rootStore={RootStore}>  // 这里你可以将mobx换为redux
       <Navigator
	  ref={registerTopNavigator}
	 />
       </Provider>
     )
    }
}

这里我通过onPressAndroidBack实现覆盖安卓默认返回事件。

  1. 找到你的TabStack的根路由,通过createBottomTabNavigator创建,代码如下
import { AsyncStorage } from 'react-native';
import { createBottomTabNavigator } from 'react-navigation';
import { routers } from '../../../constants';
import { getCurrRoutes } from '../../../utils/navigationService';

// pages 路由变量乱写的,请自行修改
import XX1Screen from './XX1';
import XX2Screen from './XX2';
import XX3Screen from './XX3';
import XX4Screen from './XX4';

const RouteConfig = {
  [routers.xx1]: XX1Screen,
  [routers.xx2]: XX2Screen,
  [routers.xx3]: XX3Screen,
  [routers.xx4]: XX4Screen,
};

const NavigatorConfig = {
  initialRouteName: routers.xx1,
  tabBarOptions: {
    activeTintColor: '#fff',
    activeBackgroundColor:  '#e4393c',
    inactiveTintColor: '#e4393c',
    inactiveBackgroundColor:  '#fff',
    labelStyle: {
      fontSize: 13,
      height: 18,
    },
    style: {
      height: 61,
      backgroundColor: '#fff',
      shadowOpacity: 0.05,
      shadowRadius: 2,
      shadowColor: '#000',
      shadowOffset: { width: 0, height: -2 },
      elevation: 2,
    },
    tabStyle: {
      flexDirection: 'column',
      flexWrap: 'nowrap',
      alignItems: 'center',
    },
  },
  navigationOptions: () => ({
    tabBarOnPress: async ({ defaultHandler, navigation }) => {
      const {
        state: {
          routeName = '',
        } = {} } = navigation;
       // 拦截路由
      if (routeName === routers.xx1 || routeName === routers.xx2) {
        const token = await AsyncStorage.getItem('token');
        const {
          currRouteMap: formRouteMap,
        } = getCurrRoutes();
        if (!token) {
          navigation.navigate(routers.auth, { fromRouteId: formRouteMap.routeName });
          return;
        }
      }
      // 默认事件
      defaultHandler && defaultHandler();
    },
  }),
};

const TabStack = createBottomTabNavigator(RouteConfig, NavigatorConfig);

TabStack.navigationOptions = {
  header: null,
};

export default TabStack;

这里通过tabBarOnPress事件,在全局的方法下拿到当前的路由,然后通过本地存储的token判断权限,就可以实现拦截权限。当然因为顶层用的createSwitchNavigator方法,在权限(AuthStack)中,你还需要应该使用以下方案navigate push压入栈中,跳转路由,而不是goBack

this.props.navigation.navigate(fromRouteId);

(四) 总结

在web中 react-router-dom提供 history方案,而react-native只能自己定义全局的路由方案,而且全局路由可以提供在状态管理中提供路由跳转方案。

因为不通过redux或者mobx依赖,那么兼容方案,就不需要考虑状态管理移植问题,况且,就单纯封装程度的解藕来看,建议能单纯用react实现的功能,比如纯组件,基本插件封装,尽量不藕合状态管理,因为我也遇到一次用别人插件,在1.3版本不集成redux,但是到1.4却全部在redux中处理,导致我使用mobx根本无法集成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值