React Native之Redux的使用

前言

Redux是一个状态管理工具,提供可预测化的状态管理。在应用中,我们经常使用stateprops来管理组件的数据流,以及组件与组件之间的数据传递,然后更新UI,通常情况下,如果应用不大,代码规范优秀,业务逻辑处理得当,那么使用stateprops没有任何问题,我们也用不着所谓的Redux来管理应用的状态。

但是,随着应用的逐渐庞大,业务逻辑变得越来越复杂,这时候,我们发现,有些组件与组件之间的值的传递(props)会变得很困难,修改了这个组件,另一个组件也要保持同步、刷新等等操作,我们会使用blockNotification缓存等操作来保持应用的最新状态,一不小心,容易出错,在这种情况下,有一个好的工具来替我们来管理整个应用的状态,那就太棒了,而Redux正是帮我们做这一复杂逻辑的一个状态管理工具,让我们可以独立地在任何地方获取到整个应用的状态。

Redux在GitHub地址。

概念

  • action

action就是一个普通的对象,用来描述某一种行为。

  • reducer

reducer是一个纯函数,用来计算、合并在产生行为后的state数据,返回一个全新的statestore。之所以叫纯函数,是因为在reducer函数中,只做数据的合并、处理操作,不会调用任何API,也不会改变外部的state数据。

  • store

store是一个存储整个应用的state树,它保存了整个应用的状态信息,重要的是,任何应用,有且只有一个store

安装

在使用之前,我们需要安装的三方库如下:

  • redux
1
npm install --save redux
  • react-redux
1
npm install --save react-redux

react-reduxredux的官方react绑定库,提供一些组件关联函数、状态传递provider等。

  • redux-thunk
1
npm install redux-thunk

redux-thunk是一个Middleware,与redux组合可调用异步函数。

使用

效果图:

gif.gif

注:假设你已经熟悉创建React Native工程,如何创建这里不再详细说明,若不懂的话可移步到:React Native官网 或者 React Native中文网

工程目录结构

注:目录可根据自己需要,并不是觉得,只要你觉得方便、合理、低耦合即可。

proj_structure

代码实现

Action目录
  • ActionType.js
1
export const ADD_NAME = 'ADD_NAME';

用于描述action的类型,也就是某种行为,一般建议用字符串常量。

  • NameAction.js
1
2
3
4
5
6
import {ADD_NAME} from "./ActionType";

export const addName = data => ({
type: ADD_NAME,
data,
});

这个文件主要用于创建action对象,根据Redux标准,每一个对象,必须有一个type字段,用于描述要触发的类型。

Reducer目录
  • NameReducer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {ADD_NAME} from "../Action/ActionType";

const initialState = {
data: [
{
title: 'test0',
},
],
};

export const nameReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_NAME:
return {
...state,
data: state.data.concat(action.data),
};
default:
return state;
}
};

创建reducer函数即nameReducer,接受先前的state,默认有一个初始值;第二个参数是action,也就是当触发操作后接受到的action对象,里面包含了类型type和数据(数据是非必须的,根据需要看是否传入)。

如果找不到类型,默认返回先前的state状态。

  • index.js
1
2
3
4
5
6
7
8
import {combineReducers} from 'redux';
import {nameReducer} from './NameReducer';

const rootReducer = combineReducers({
nameReducer,
});

export default rootReducer;

通过redux提供的combineReducers将所有的reducer组合成一个rootReducer(这里为了方便,只写了一个reducer)。

Store目录
  • Store.js
1
2
3
4
import {createStore} from 'redux';
import rootReducer from "../Reducer";
const store = createStore(rootReducer);
export default store;

store的职责是维持整个应用的状态,创建store也很简单,直接使用Redux提供的函数createStore,传入上一步创建的rootReducer即可,这样,redux相关的东西准备得差不多了,接下来该在我们的组件里面,绑定我们的store、触发action就好了。

Component目录
  • 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import React, {Component} from 'react';
import {
View,
FlatList,
TouchableOpacity,
Text,
} from 'react-native';
import {connect} from 'react-redux';
import {addName} from '../Action/NameAction';
import styles from "./styles";

const buttonText = 'Add Name';
let index = 0;

class Home extends Component {

_addName = () => {
index += 1;
const data = [{title: `test${index}`}];
this.props.addName(data);
};

_renderItem = ({item}) => {
const {title} = item;
return (<View style={styles.cell}>
<Text>{title}</Text>
</View>);
};

render() {
return (
<View style={styles.container}>
<View style={styles.navigationBar}/>
<TouchableOpacity onPress={this._addName}>
<Text style={styles.nameButtonText}>{buttonText}</Text>
</TouchableOpacity>
<View style={styles.line}/>
<FlatList
renderItem={this._renderItem}
data={this.props.data}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}

}

const mapStateToProps = state => ({
data: state.nameReducer.data,
});

const mapDispatchToProps = dispatch => ({
addName: data => dispatch(addName(data))
});

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

在这个组件中,最重要的一个步骤是使用react-redux提供的connect函数,将Home组件和我们的redux关联起来,这样就可以获取到store中的state,触发action

mapStateToProps: 主要用于将store中的state映射到当前组件的props,这样的话,我们就可以在组件中通过this.props的形式获取需要的数据。state.nameReducer.data中的nameReducer就是Reducer目录下的index.js里面的nameReducer,最终其实就是NameReducer.js文件中的纯函数nameReducer,里面的data也就是从store取出来的最新state数据,在render函数里面,我们使用this.props.data拿到最新的列表数据。

mapDispatchToProps: 主要用于将dispatch(action)操作映射到当前的Home组件中,我们可以看到,在_addName点击事件里面,使用了this.props.addName(data);,这个this.props.addName就是mapDispatchToProps的功劳。dispatch(addName(data))中的addName就是之前我们创建的action对象(你也可以理解成一个函数),即:

1
2
3
4
export const addName = data => ({
type: ADD_NAME,
data,
});

这样的话,当我们点击按钮来调用_addName的使用,通过dispatch来触发addName()这个action,然后触发nameReducer,最后通过this.props.data获取到最新的state数据来刷新UI

  • styles.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
import {StyleSheet} from 'react-native';

export default StyleSheet.create({
container: {
flex: 1,
},
navigationBar: {
height: 88,
},
nameButtonText: {
height: 50,
padding: 15,
backgroundColor: 'black',
color: 'white',
alignSelf: 'center',
borderRadius: 10,
},
cell: {
height: 50,
justifyContent: 'center',
marginLeft: 15,
marginRight: 15,
borderBottomWidth: 0.5,
borderBottomColor: 'blue',
},
line: {
marginTop: 15,
backgroundColor: 'green',
height: 0.5,
},
});
App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, {Component} from 'react';
import Home from "./Redux/Component/Home";
import {Provider} from 'react-redux';
import Store from './Redux/Store/Store';

export default class App extends Component {

render() {
return (
<Provider store={Store}>
<Home/>
</Provider>
);
}
}

App.js是最重要的一个节点,因为它是我们的根组件,我要要使用Redux并在全局的其他子组件获取到state,那么就要在根组件的最外层包括一个Provider容器,不必担心,这个在react-redux中已经给我们准备好了,我们要做的就是直接导入使用即可。

这里的Store也就是我们之前通过createStore(rootReducer)创建的并且唯一的一个store,它在全局起着至关重要的作用,因为我们所有的数据都是存放在这个store中,它维持的是一棵完整的state树,有了它,你可以在其他任意组件通过connect获取到当前store中存储的数据和状态。

小结

  • action是一个对象,用于描述某一个动作或行为,可以传入数据
  • reducer是一个纯函数,主要用于处理state数据,返回全新的state数据
  • store整个应用中只有一个,维持着所有的state数据,我们可以在任意组件通过connect获取

本文虽然引入了redux-thunk,由于篇幅有限,并未使用,放在下一篇文章中进行讲解。

本文源码:RN_ReduxDemo