茗空间

如何在ReactNative中使用redux




碎碎念

image

近日王宝强(本来是想用‘宝宝’来称呼,但是我为人高冷,还是用原名吧)的离婚闹得沸沸扬扬,热度是不是已经远远超过了里约奥运。无下限的网友各种恶搞,从‘潘金莲照顾武大郎’到‘宝宝捉奸’,只说一句:万能的网友捉奸视频有没有?刚看到王宝强的微博,只想说:唉,下贱的小娼妇,但是细想与我何干?毕竟我不知道谁是谁非,这里不做评判。

导语


ReactNative也出了一年多了,facebook还在忙碌的修复各种问题,截止目前github上还有900个issue未解决。对于我而言对RN还是寄托了很大的期。
ReactNative是基于状态的组件化框架,随着RN项目变大,state变得不可预测,不可预测的意思是state到处修改和使用导致
维护与debug很困难,所以就要有个管理state的方式,
这种方式就是Redux。但注意一点Redux并不仅仅为RN而生。
Notice:本文更适合有RN基础的朋友,如果你学过reactjs也是可以的。

下面解释一些重要概念,然后解析一个demo

Redux


Redux是为javascript而生的可预测的状态容器,是Flux的进化。什么是Flux?没用过。为什么是可预测的?下面会解释。

Redux由Action、Reducer、Storage三部分组成,先来看看官方代码(里面有中文的珠玑文字,不要一目十行,请慢慢体会):

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import { createStore } from 'redux'
/*
*
* 代码里没定义action,action类似于如下,仅仅是一个普通对象
*
* 但是要有**type**,因为reducer(下面马上就会提到)会根据type来更改state
*
*/
let action = {
type: INCREMENT,
params: xxxx,
};
/**
* This is a reducer, a pure function with (state, action) => state signature.
* It describes how an action transforms the state into the next state.
*
* The shape of the state is up to you: it can be a primitive, an array, an object,
* or even an Immutable.js data structure. The only important part is that you should
* not mutate the state object, but return a new object if the state changes.
*
* In this example, we use a `switch` statement and strings, but you can use a helper that
* follows a different convention (such as function maps) if it makes sense for your
* project.
*/
/*
*
* 只有reducer才能更改state;
*
* 返回值为下一个状态,注意下一个state不是对当前状态修改后返回,而是返回了一个全新的state,
* 也就是说redux中的state是不可改变的,如果想改变状态那就返回一个全新的state
*
*/
function reducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
/*
* store就用来存放state的容器
* createStore(reducer) 参数是reducer,把reducer注入到store,这样store最终就成为保存和修改state的容器了
* 什么是注入?其实就是把reducer赋值给store中的某个变量了,为什么这么做可以google关键字:**设计模式 依赖注入 控制反转**
*/
let store = createStore(reducer)
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// However it can also be handy to persist the current state in the localStorage.
/*
*
* 订阅:其参数是一个回调函数,当state改变时就会触发这个回调函数
* 订阅有个好处(如上英文注释),可以很方便的持久化当前state到localStorage(即将state保存到localStorage)
*
*/
store.subscribe(() =>
console.log(store.getState())
)
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
/*
*
* dispatch: 参数为action,store会将此action作为参数执行reducer(上面已经通过createStorage传入到store了)
*
*/
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1

数据流动的方向时:action->store->store里的reducer(用来改变state)->更新UI(因为RN是基于状态机的,所以state更改后自动render)

这也很容理解上面的可预测是什么意思,数据流保证了单向流动,store集中管理state,除了reducer没有任何方法修改state,还有重要的一点是state是不可改变的,因为reducer返回的下一个状态state2是基于上一个状态state1的,而不是修改state1然后返回state1.

看完之后有的人酱紫:
image

还有的人酱紫:
image

如果不理解,多看两遍,下面会带着大家一起写个demo。

先来欣赏一段扎克伯格的文字
People often ask me what advice I’d give someone who wants to start their own company.
My answer is that every good company that I can think of started with someone caring about changing something, not someone deciding > to start a company. Instead of trying to build a company, focus on the change you want to see in the world and just keep pushing
forward.
有人问我这段字有什么含义,我说:没含义,放松一下😌

Redux Thunk

github地址在这里

什么是thunk? thunk是一个包含了表达式(expression)的函数,用来延迟表达式(expression)的执行。

1
2
3
4
5
6
7
8
9
10
// 这里1+2是立即执行的
// calculation of 1 + 2 is immediate
// x === 3
let x = 1 + 2;
// 这里1+2不会立即执行,只有在调foo时才会执行
// calculation of 1 + 2 is delayed
// foo can be called later to perform the calculation
// foo is a thunk!
let foo = () => 1 + 2;

现在一些框架的readme在写其用处时一般用Motivation这个词,动机纯不纯的动机。




Redux Thunk middleware允许你的action creator返回一个function而不是action。

Thunk 可以用来延迟dispatch一个action,或者只有满足某个特定条件时才dispatch。

inner function把store的dispatch和getState作为参数

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// action creator 返回一个function去执行异步dispatch
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
1
2
3
4
5
6
7
8
9
10
11
12
// action creator返回一个function去有条件的执行dispatch
function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();
if (counter % 2 === 0) {
return;
}
dispatch(increment());
};
}

Demo->Discolor

进这里,点击右上角的star
实现的功能是,页面上有两个按钮,点击按钮页面背景变色,第一个是变成红色,第二个是变成绿色

在此demo的同级目录下还有个Counters,这个示例稍微复杂一点。

再往下看前,请打开你已经down下来的demo

先来看看文件目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-app
---actions
-----actionTypes.js : action类型
-----discolorActions.js : action creators
---components
-----discolor.js : 纯ReactNative自定义组件
---containers
-----app.js :加入Provider
-----discolorApp.js :定义app
---reducers
-----discolor.js : reducer之一
-----index.js : export所有reducer,当然这里只有一个discolor
-index.ios.js
-index.android.js

安装依赖此处为必要的安装,如果缺少,会有red screen提示的

1
2
npm install --save redux
npm install --save react-redux

actions/actionTypes.js定义了两种类型:变成红色 和 变成绿色

1
2
export const CHANGE2RED = 'CHANGE2RED';
export const CHANGE2GREEN = 'CHANGE2GREEN';

actions/discolorActions.js定义了两个action creator,如果你是从头看的,

现在应该能看懂了,如果不懂,那就从头看吧

1
2
3
4
5
6
7
8
9
10
11
12
13
import * as types from './actionTypes';
export function change2red() {
return {
type: types.CHANGE2RED,
}
}
export function change2green() {
return {
type: types.CHANGE2GREEN,
}
}

components/discolor.js是RN的自定义控件

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
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
TouchableHighlight,
} from 'react-native';
export default class Discolor extends Component {
constructor(props) {
super (props);
}
render() {
// 这些属性是从DiscolorApp传过来的
const { color, change2red, change2green} = this.props;
return (
<View style={[styles.container, {
backgroundColor: color,
}]}>
<TouchableHighlight onPress={change2red} style={styles.button}>
<Text>change2red</Text>
</TouchableHighlight>
<TouchableHighlight onPress={change2green} style={styles.button}>
<Text>change2green</Text>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'purple',
},
button: {
width: 120,
height: 40,
backgroundColor: 'lightgray',
alignItems: 'center',
justifyContent: 'center',
margin: 3,
},
});

containers/discolorApp.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
'use strict' // javascript的严格模式
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import Discolor from '../components/discolor';
import * as discolorActions from '../actions/discolorActions';
import { connect } from 'react-redux';
class DiscolorApp extends Component {
constructor(props) {
super(props);
}
render() {
const { state, actions } = this.props;
return (
<Discolor
color={state.color}
{...actions} />
);
}
}
/*
*
* connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
*
* 这里只说第一个参数,其他的参考:http://cn.redux.js.org/docs/react-redux/api.html
*
* [mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。
* 任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。
* 该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。
* 如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props,mapStateToProps 也会被调用。
*
* 所以这里state: state.discolor中的discolor其实就是reducers/discolor.js
* 一旦Redux store变化这个回调函数就会执行discolor,discolor就会返回新的state,
* 为什么discolor会返回新的state,因为它是reducer
*/
export default connect(state => ({
state: state.discolor
}),
(dispatch) => ({
/*
* 再来说下 bindActionCreators 的作用: 把 action creators 转成拥有同名 keys 的对象,但使用 dispatch 把每个 action creator 包围起来,这样可以直接调用它们。
* 什么意思呢?在这里的bindActionCreators返回值就是普通对象:
* {
* change2red: dispatch(discolorActions.change2red),
* change2green: dispatch(discolorActions.change2green),
* }
*/
actions: bindActionCreators(discolorActions, dispatch),
})
)(DiscolorApp);

containers/app.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 React, { Component } from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';
import DiscolorApp from './discolorApp';
/*
*
* 通过使用applyMiddleware来允许使用thunk
*
*/
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
/*
*
* 合并多个reducer
*
*/
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<DiscolorApp />
</Provider>
);
}
}

reducers/discolor.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
/*
*
* 项目里唯一的reducer
*
*/
import * as types from '../actions/actionTypes';
const initialState = {
color: 'red',
};
export default function discolor(state = initialState, action = {}) {
switch (action.type) {
case types.CHANGE2RED:
return {
...state,
color: 'red',
};
case types.CHANGE2GREEN:
return {
...state,
color: 'green',
};
default:
return state;
}
}

reducers/index.js:为了可以统一导出所有reducer,这里只有discolor

如何统一导出,在containers/app.js中是这么用的: import as reducers from ‘../reducers’;*

1
2
3
4
5
import discolor from './discolor';
export {
discolor,
};

index.ios.js:代码与index.android.js相同

1
2
3
4
5
6
7
'use strict' // javascript严格模式
import React from 'react';
import { AppRegistry } from 'react-native';
import App from './app/containers/app'
AppRegistry.registerComponent('ReactNativeReduxSimplest', () => App);

结语

以上就是关于ReactNative结合Redux的入门教程,希望可以帮助大家理清思路。我本着用最简单的方式表达最重要的信息的目的来撰写此文,在有些措辞不严谨或者表达不够准确的地方希望大家可以留言。

参考

redux 英文版本:如果你不喜欢请看参考2

redux 中文版本:翻译版,系统讲解

stackoverflow问题:ES6能否import一个目录(多个文件)

Redux Thunk: 什么是Thunk?

example-react-native-redux : demo参考了这个项目

“ES6能否import一个目录(多个文件)”