从零开始搭建react-native+redux+immutable+react-navigation项目

  • Lanceloft
  • 45 Minutes
  • June 7, 2018

开头介绍

  虽然网上到处都是react-native的教程, 也到处都是redux的教程, immutable的介绍也是十分的多, 但是把以上所有结合的教程是少之又少, 寥寥几篇都是英文文档, 对于新手来说是相当不友好. 而且大部分介绍都是理论性知识, 并没有直接一个demo的展示, 当初相当困扰, 现在跑通了问题就把积累的经验写一下教程. 如果能帮到人就是很好的.
  redux是react的状态管理库, 如果光使用react的话我觉得使用mobx比redux更简单方便, 只是因为app性能消耗大, 需要用不可变数据(immutable)的处理, 而mobx是可变数据的库, 两者并不结合,所以采用redux. navigation方面react-navigation就十分方便. 官方推荐的是react-navigation和react-native-navigation, 两者的使用方式类似, 以下先把使用的库的文档的地址发出来.

react-native: https://facebook.github.io/react-native/
redux: https://redux.js.org/
immutable: https://facebook.github.io/immutable-js/
react-navigation: https://reactnavigation.org/

创建项目

  本文章使用操作系统macOS High Sierra 10.13.4.
  采用create-react-native-app进行创建项目, 与react-native-cli对比, create-react-native-app集成了expo所以更加方便快捷, 但是问题在于打包的时候必须要借助expo或者xcode/Android studio进行打包. 而使用react-native-cli则可以通过命令行进行打包. 实际项目使用个人推荐react-native-cli, 但平时的练习可以使用create-react-native-app. 因为使用了yarn, 以下都是yarn的环境.

yarn global add create-react-native-app
 create-react-native-app demoProject
 cd demoProject
 yarn start

路由配置

  执行后根据终端提示点击i打开模拟器, 然后就能图片下的这样子,证明代码已经成功运行. 现在开始安装react-navigation作为路由管理.

yarn add react-navigation  

初始运行
初始代码

  可以看到现在项目的代码现在都在App.js上面, 我们开始新建文件夹进行分类. 首先新建src文件夹, 里面新建navigations文件夹, 里面新建Index.js文件.

1
2
3
4
5
6
7
8
9
10
11
12
// Index.js
import React from 'react';
import { Text, View } from 'react-native';
import { createBottomTabNavigator } from 'react-navigation';

import HomeScreen from '../pages/Home';
import MineScreen from '../pages/Mine';

export default createBottomTabNavigator({
Home: HomeScreen,
Mine: MineScreen,
});

修改App.js, 将组建指向路由组建

1
2
3
4
5
6
7
8
9
10
11
// App.js
import React from 'react';
import AppNavigation from "./src/navigations/Index";

export default class App extends React.Component {
render() {
return (
<AppNavigation />
);
}
}

新建src/pages文件夹, 新建Home和Mine页面, 作为navigation的指向页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/pages/Home.js
import React from 'react';
import { View, Text } from 'react-native';

class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>HomeScreen!</Text>
</View>
);
}
}

export default HomeScreen;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/pages/Mine.js
import React from 'react';
import { View, Text } from 'react-native';

class MineScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>MineScreen!</Text>
</View>
);
}
}

export default MineScreen;

此时目录结构如下

路由目录

同时看到模拟器已经转为下图

路由页面

到此路由配置完成.

状态管理

  接着开始redux的配置. 这里写一个简单的数字增加减少demo进行演示状态的分发. 首先需要安装redux, react-redux, 还有redux-thunk

yarn add redux
 yarn add react-redux  
 yarn add redux-thunk

因为redux需要在全局注册, 这里需要修改App.js文件, 增加全局控制的store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// App.js
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from './src/store/Index';
import AppNavigation from "./src/navigations/Index";

const store = configureStore();

export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppNavigation />
</Provider>
);
}
}

在src/store增加Index.js文件, 作为控制store状态集中管理

1
2
3
4
5
6
7
8
9
10
// src/store/Index.js
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from '../reducers/Index';

const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);

export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}

新增src/reducers/Index.js作为reduces的管理, reducers指定了应用状态的变化如何响应action并分发到store. 用rootReducer包含所有的reducers可以方便管理不同页面的状态.

1
2
3
4
5
6
7
8
9
// src/reducers/Index.js
import { combineReducers } from 'redux';
import homeState from './Home';

const rootReducer = combineReducers({
homeState,
});

export default rootReducer;

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/reducers/Home.js
const initState = {
number: 1,
};

const mainReducer = (state = initState, action) => {
switch (action.type) {
default:
return state
}
};

export default mainReducer;

至此, store的状态的就配置完成, 这时候在页面就可以获取对应的store值,我们回到src/pages/Home.js进行配置, 拿到存在reducers里面的number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/pages/Home.js
import React from 'react';
import { View, Text } from 'react-native';
import { connect } from 'react-redux';

class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>HomeScreen!</Text>
<Text>{ this.props.number }</Text>
</View>
);
}
}

const mapStateToProps = (state) => {
return {
number: state.homeState.number,
}
}

export default connect(mapStateToProps)(HomeScreen);

状态获取

出现的页面应该应可以显示我们在reducers里面的number为1, 目录结构如下.

状态代码

接着就是通过action的分发实现数字的增减. 在src/pages/Home.js增加点击增减的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/pages/Home.js
import React from 'react';
import { View, Text } from 'react-native';
import { connect } from 'react-redux';
import {
addNumber,
reduceNumber
} from '../actions/Actions';

class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>HomeScreen!</Text>
<Text>{ this.props.number }</Text>
<Text onPress={ this.props.onAdd }>ADD</Text>
<Text onPress={ this.props.onReduce }>REDUCE</Text>
</View>
);
}
}

const mapStateToProps = (state) => {
return {
number: state.homeState.number,
}
}

const mapDispatchToProps = (dispatch) => {
return {
onAdd: () => dispatch(addNumber()),
onReduce: () => dispatch(reduceNumber()),
}
}

export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen);

这里通过mapDispatchToProps把组件状态进行传递过去. 在src新建actions文件夹, 里面新增Actions.js和Types.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/actions/Actions.js
import * as types from './Types';

export const addNumber = () => {
return {
type: types.ADD_NUMBER
}
}

export const reduceNumber = () => {
return {
type: types.REDUCE_NUMBER
}
}

1
2
3
// src/actions/Types.js
export const ADD_NUMBER = 'ADD_NUMBER';
export const REDUCE_NUMBER = 'REDUCE_NUMBER';

然后在src/reducers/Home.js进行引入types, 修改刚才已经定义的number的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/reducers/Home.js
import {
ADD_NUMBER,
REDUCE_NUMBER
} from '../actions/Types';

const initState = {
number: 1,
};

const mainReducer = (state = initState, action) => {
switch (action.type) {
case ADD_NUMBER:
return {
...state,
number: state.number + 1
}
case REDUCE_NUMBER:
return {
...state,
number: state.number - 1
}
default:
return state
}
};

export default mainReducer;

redux代码

在模拟器上进行点击, 发现已经可以通过ADD和REDUCE进行把number的值进行增减.到此redux已经配置完成. 目录结构此时如下

redux代码

接下来就是关于immutable的配置, 关于immutable的介绍网上已经很多, 就不需要再过多的介绍,这里直接开始讲述如何进行配置.

immutable配置

安装依赖库

yarn add immutable
 yarn add redux-immutable

immutable是将数据改为不可变数据, 因此需要操作的主要在reducers一块.这里需要先把src/reducers/Index的combineReducers改为使用redux-immutable的combineReducers

1
2
3
4
5
6
7
8
9
// src/reducers/Index.js
import { combineReducers } from 'redux-immutable';
import homeState from './Home';

const rootReducer = combineReducers({
homeState,
});

export default rootReducer;

然后到src/reducers/Home.js修改state改为immutable使用的数据格式, 同时action修改state的方式也要改为immutable支持的格式, 即将js数据类型转换immutable数据类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/reducers/Home.js
import { Map } from 'immutable';
import {
ADD_NUMBER,
REDUCE_NUMBER
} from '../actions/Types';

const initState = Map({
number: 1,
});

const mainReducer = (state = initState, action) => {
switch (action.type) {
case ADD_NUMBER:
return state.set('number', state.get('number') + 1)
case REDUCE_NUMBER:
return state.set('number', state.get('number') - 1)
default:
return state
}
};

export default mainReducer;

这时候关于redux转换的已经完成了, 还差最后一步就是在组件里面修改获取immutable的方式.这里采用官方getIn的方式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// src/pages/Home.js
import React from 'react';
import { View, Text } from 'react-native';
import { connect } from 'react-redux';
import {
addNumber,
reduceNumber
} from '../actions/Actions';

class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>HomeScreen!</Text>
<Text>{ this.props.number }</Text>
<Text onPress={ this.props.onAdd }>ADD</Text>
<Text onPress={ this.props.onReduce }>REDUCE</Text>
</View>
);
}
}

const mapStateToProps = (state) => {
return {
number: state.getIn(['homeState', 'number']),
}
}

const mapDispatchToProps = (dispatch) => {
return {
onAdd: () => dispatch(addNumber()),
onReduce: () => dispatch(reduceNumber()),
}
}

export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen);

至此immutable的配置也已经完成, 关于更多redux, immutable的使用的方法请查看官网文档, 这里不作更多解释. 本项目例子github已放github仓库 https://github.com/Lanceloft/react-native-demo 欢迎star