reactnative之react-navigation和redux实践

RN开发一般都会结合一些处理数据流的插件库,如redux、mobx、dva等,dva基于 redux和 redux-saga,内置了 react-router和 fetch;mobx使用简单灵活易上手;而redux,很多新手都觉得不仅要写多一些代码,而且集成起来还有点麻烦,其实不然,可能封装或者使用不当,造成滥用redux,导致看起来繁琐。本篇博文从应用实践出发,介绍react-navigation和redux在RN中的使用。

RN主要有两个路由库react-navigation、react-native-router-flux,后者其实也是基于前者进行封装的,但是使用起来更加简单,文档介绍方面肯定就没有react-navigation写的具体了,所以本人建议入门使用react-navigation。

package.json引入以下库:

 "react-native-gesture-handler": "^1.0.12",
 "react-native-reanimated": "^1.13.2",
 "react-navigation": "^4.4.3",
 "react-navigation-drawer": "^2.6.0",
 "react-navigation-redux-helpers": "^4.0.1",
 "react-navigation-stack": "^2.10.2",
 "react-navigation-tabs": "^2.10.1",
 "react-redux": "5.1.1",
 "redux": "^4.0.1",
 "redux-logger": "^3.0.6",
 "redux-thunk": "^2.3.0"

一、react-navigation

1、创建底部Tab

  • createBottomTabNavigator
    底部Tab无非两种,如下图所示:


    tab1.jpg
tab2.jpg

实现Tab1的代码如下:

import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { createDrawerNavigator } from 'react-navigation-drawer';

const TabNavigator = createBottomTabNavigator({
    Home: {
        screen: HomeScreen,
    },
    Goods: {
        screen: GoodsScreen,
    },
    Message: {
        screen: MessageScreen,
    },
    Mine: {
        screen: MineScreen,
    }
}, {
    defaultNavigationOptions: ({ navigation }) => ({
        tabBarIcon: ({ focused, horizontal, tintColor }) => {
            const { routeName } = navigation.state;
            let icon;
            if (routeName === 'Home') {
                icon = focused ? Images.tab.home_sel : Images.tab.home
            } else if (routeName === 'Goods') {
                icon = focused ? Images.tab.goods_sel : Images.tab.goods
            } else if (routeName === 'Message') {
                icon = focused ? Images.tab.message_sel : Images.tab.message
            } else if (routeName === 'Mine') {
                icon = focused ? Images.tab.mine_sel : Images.tab.mine
            }
            return 
        },
    }),
    tabBarOptions: {
        initialRouteName: 'Home',
        activeTintColor: colors.activeTintColor,
        inactiveTintColor: colors.inactiveTintColor,
    }
});
const AppNavigator = createStackNavigator({
    Main: {
        screen: TabNavigator,
    },
    Login: {
        screen: Login
    },
}, {
    mode: 'modal',
    headerMode: 'none',
});
export default AppContainer = createAppContainer(AppNavigator); 

实现Tab2的效果需要自定义tabBarComponent,代码如下:

const TabNavigator = createBottomTabNavigator({
    Home: {
        screen: HomeScreen,
    },
    Goods: {
        screen: GoodsScreen,
    },
    Message: {
        screen: MessageScreen,
    },
    Mine: {
        screen: MineScreen,
    }
}, {
     tabBarComponent: (props) => (
         
     )
});
  • MyCustomTaBar的实现也很简单,让UI设计一张特殊的背景图即可,代码如下:
import React, { Component } from 'react';
import { View, Text, ImageBackground, Image, StyleSheet } from 'react-native';
import { TouchableOpacity, TouchableWithoutFeedback } from 'react-native-gesture-handler';

import { colors } from '../../common/theme/color';
import { Images } from '../../image';
export default class MyCustomTaBar extends Component {

    render() {
        // console.log(JSON.stringify(this.props));
        const { state } = this.props.navigation
        state.routes.forEach((e, index) => {
            if (state.index == index) {
                e.focused = true
            } else {
                e.focused = false
            }
        });
        return (
            
                
                    
                        {
                            state.routes.length > 0 && state.routes.map((item, index) => {
                                return 
                            })
                        }
                    
                
            
        );
    }
}
const Item = class extends Component {
    getIcon = () => {
        const { routeName, focused } = this.props;
        let icon;
        if (routeName === 'Home') {
            icon = focused ? Images.tab.home_sel : Images.tab.home
        } else if (routeName === 'Goods') {
            icon = focused ? Images.tab.goods_sel : Images.tab.goods
        } else if (routeName === 'Message') {
            icon = focused ? Images.tab.message_sel : Images.tab.message
        } else if (routeName === 'Mine') {
            icon = focused ? Images.tab.mine_sel : Images.tab.mine
        }
        return icon
    }
    getName = () => {
        const { routeName, focused } = this.props;
        let name;
        if (routeName === 'Home') {
            name = '首页'
        } else if (routeName === 'Goods') {
            name = '好货'
        } else if (routeName === 'Message') {
            name = '消息'
        } else if (routeName === 'Mine') {
            name = '我的'
        }
        return name
    }
    gotoRoute = (routeName) => {
        this.props.navigation.navigate(routeName)
    }
    render() {
        const { routeName, focused } = this.props;
        if (routeName == 'Goods') {
            return ( { this.gotoRoute(routeName) }}
                style={{
                    // flex: 1,
                    height: 100,
                    width: SCREEN_WIDTH / 3,
                    justifyContent: 'center',
                    alignItems: 'center',
                    top: -30,
                    backgroundColor: 'transparent'
                }}>
                
                    
                    {/*  */}
                
                
                    {this.getName()}
                
            )
        }
        return (
             { this.gotoRoute(routeName) }}
                style={{
                    flex: 1,
                    width: SCREEN_WIDTH / 3,
                    justifyContent: 'center',
                    alignItems: 'center',
                }}>
                
                {this.getName()}
            
        )
    }
}
const styles = StyleSheet.create({
    activeTintColor: {
        color: colors.activeTintColor,
        fontSize: 12,
    },
    inactiveTintColor: {
        color: colors.inactiveTintColor,
        fontSize: 12
    }
});

2、创建抽屉

  • createDrawerNavigator
    抽屉组件的使用无非就是开启关闭:

this.props.navigation.openDrawer()
this.props.navigation.closeDrawer()

集成代码如下所示:

const DrawerNavigator = createDrawerNavigator({
    Main: {
        screen: AppNavigator,
    },
    drawerA: {
        screen: DrawerScreen
    },
    drawerB: {
        screen: DrawerBScreen
    },
}, {
    order: ['Main', 'drawerA', 'drawerB'],//定义抽屉项目的顺序
    initialRouteName: 'Main',
    drawerType: 'front',
    drawerLockMode: 'unlocked',//是否响应手势
    drawerWidth: 250, //抽屉的宽度
    drawerPosition: 'left', //选项是left或right
    useNativeAnimations: true, //启用原生动画
    drawerBackgroundColor: colors.theme, //抽屉背景颜色
    contentComponent: (props) => ()
});
export default AppContainer = createAppContainer(DrawerNavigator);

DrawerBScreen组件是你自定义的页面。
有个不足之处就是react-navigation自带的抽屉组件不支持手势返回,所以建议使用react-native-drawer-layout,效果还不错。

react-navigation的使用就点到为止,本文不是为了介绍每个api的使用,旨在阐述一些常见的应用场景,建议新手过一遍官方文档。

二、redux

redux有三大原则:

  • 单一数据源:整个应用的state统一放在一个store中
  • State 是只读的:只能通过派发action来改变state
  • 使用纯函数来执行修改:Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
    官方示例代码可以去看下:redux示例代码,这里只展示redux在RN中的应用。

在RN中集成redux,步骤如下:

  • createStore--创建一个store
import {
    createStore,
    applyMiddleware
} from 'redux';
import thunk from "redux-thunk"

import {
    createReactNavigationReduxMiddleware,
} from 'react-navigation-redux-helpers';
import appReducer from './reducers/index'

const middleware = createReactNavigationReduxMiddleware(
    state => state.nav,
    'root'
);
const middlewares = [
    middleware,
    thunk
]

const store = createStore(
    appReducer,
    applyMiddleware(...middlewares),
);

export default store
  • 封装appReducer,所有reducer统一放在这里
import { combineReducers } from 'redux';
import {navReducer} from './navReducer'
import {login} from './loginReducer'
const appReducer = combineReducers({
    nav:navReducer,
    login:login
});
export default appReducer
  • 创建navReducer,存放路由相关数据
import {
    createNavigationReducer,
} from 'react-navigation-redux-helpers';
import  AppContainer  from '../../router/index' //这里的AppContainer就是上面展示的路由相关配置的代码

export const navReducer = createNavigationReducer(AppContainer);

  • 以loginReducer为例子阐述reducer的整个流程:
    1、新建actionTypes
//登录相关action
export const LOGINING = 'LOGINING'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGIN_ERROR = 'LOGIN_ERROR'
export const LOGOUT = 'LOGOUT'

2、新建要派发的action函数

import * as actionType from '../actionsTypes/index'
import { LoginInfo } from '../../redux/reducers/loginReducer'

export function login(name, psw) {
    // console.log(name, psw);
    return dispatch => {
        //登录中
        dispatch(logining())
        fetch('https://www.baidu.com/', 'get')
            .then(res => {
                dispatch(loginSuccess({
                    name, psw
                }))
            })
            .catch(e => {
                dispatch(loginFail())
            })
    }
}
export function logining() {
    return {
        type: actionType.LOGINING
    }
}

export function loginSuccess(userInfo) {
    return {
        type: actionType.LOGIN_SUCCESS,
        state: userInfo
    }
}

export function loginFail() {
    return {
        type: actionType.LOGIN_ERROR
    }
}

export function loginOut() {
    return {
        type: actionType.LOGOUT
    }
}

3、reducer改变state并返回

import * as type from '../actionsTypes/index'

export const LoginInfo = {
    status: "未登录",
    isLogin: false,
    user: {},
};
export const login = function (state = LoginInfo, action) {
    switch (action.type) {
        case type.LOGINING:
            return {
                ...state,
                status: "登录中",
                isLogin: false,
            };
        case type.LOGIN_SUCCESS:
            return {
                ...state,
                status: "登陆成功",
                isLogin: true,
                user: action.state
            };
        case type.LOGIN_ERROR:
            return {
                ...state,
                status: "登录失败",
                isLogin: false,
                user: {}
            };
        case type.LOGOUT:
            return {
                ...state,
                status: "未登录",
                isLogin: false,
                user: {}
            };
        default:
            return state;
    }
}

4、最后就是在你的页面中通过connect来访问reducer

const mapStateToProps = (state) => ({
    nav: state.nav,
    status: state.login.status,
    user: state.login.user
})

const mapDispatchToProps = dispatch => ({
    login: (name, psd) => dispatch(actions.login(name, psd)),
    loginOut: () => dispatch(actions.loginOut())
});

export default connect(mapStateToProps, mapDispatchToProps)(Login)

不知道大家有没有注意到一个写代码的小技巧,如果你是用VSCode开发react,在新建一个react组件的时候,敲打rcredux会索引很快敲出一个react包含redux的组件出来,很省事。当然还有很多类似这个生成代码的,vue也有。有点类似.vue文件下,输入vbase可以快速生成模板代码。

三、react-navigation和redux双剑合璧

有了以上基础,react-navigation和redux实现双剑合璧就容易多了,实现代码如下:

import React, { Component } from 'react';
import { StatusBar, BackHandler, ToastAndroid } from 'react-native';
import { Provider, connect } from 'react-redux'
import store from './redux/index'
import AppContainer from './router/index'
import { NavigationActions } from 'react-navigation';
import {createReduxContainer} from 'react-navigation-redux-helpers'

const AppWithRedux=createReduxContainer(AppContainer,'root')
const mapStateToProps = (state) => ({
    state: state.nav,
  });
const AppWithNavigationState = connect(mapStateToProps)(AppWithRedux)
export default class App extends Component {
    constructor(props) {
        super(props)
        this.lastBackPressed = null
    }

    componentDidMount() {
        BackHandler.addEventListener("hardwareBackPress", this.onBackPress);

    }
    componentWillUnmount() {
        BackHandler.removeEventListener("hardwareBackPress", this.onBackPress);
    }

    onBackPress = () => {
        // alert(JSON.stringify(store.getState()))
        if (store.getState().nav.index !== 0) {
            store.dispatch(NavigationActions.back());
            return true
        }

        //退出应用
        if (this.lastBackPressed && this.lastBackPressed + 2000 >= Date.now()) {
            //最近2秒内按过back键,可以退出应用。
            return false;
        }

        this.lastBackPressed = Date.now();
        ToastAndroid.show('再按一次退出应用', ToastAndroid.SHORT);
        return true;

    };
    render() {
        return (
            
                
            
        )
    }
}

  • 提供了一个Provider组件,最外层传入store,这样以下的所有子组件都可以拿到reducer的state,原理就是react的context
  • 需要注意这里的key--'root',要跟上面的配置store的key一致:

const AppWithRedux=createReduxContainer(AppContainer,'root')

const middleware = createReactNavigationReduxMiddleware(
state => state.nav,
'root'
);

  • 安卓还要写一个返回物理键返回监听,控制是否退出应用和返回页面

如果你还是嫌弃redux麻烦,那么mobx也是较好的选择。可以参考下面的demo进行配置mobx:
https://github.com/vonovak/react-navigation-mst-demo

你可能感兴趣的:(reactnative之react-navigation和redux实践)