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的代码如下:
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