React 中使用 Redux 入门

Redux 适合用于前端业务非常复杂,或者需要从多个数据源请求数据等需求。Redux 将整个应用所需要用到的数据组织存放在一个叫做 state 的对象里面,每次从服务器请求数据或者用户交互使数据发生改变之后,都需要先发送一个 action,然后在 action 处理函数中更新 state 对象。和常规做法不同的是,我们并不需要手动去更新 UI,更新 UI 的过程由中间件去完成,state 对象改变之后,UI 会自动更新。

重要对象及概念

  • state : 整个应用程序的数据源,通常是一个普通的 Object
  • action : 当用户操作导致或者服务器数据改变需要同步更新 state 对象时,需要发送一个 action 对象(其实就是一个普通的 Object 对象)。通常 action 使用 type 属性用于区分不同的 action 动作,如果需要传递数据的话,则按相应的规定传递数据即可;
  • reducer : 处理 action 的函数,在该函数中,需要根据不同的 action 及参数,对 应用程序 state 对象进行更新,需要注意的是,不要直接改变原有的 state,而是要返回一个新的 state
  • store : 用于管理所有的数据变更,UI更新流程,一个应用中只有一个 store 对象,该对象使用 reduxcreateStore 函数生成;
  • createStore() : 创建 store 并初始化 state,该方法会触发处理 statereducer 函数,对 state 进行初始化,此时 reducer 函数接收到的 action.type @@redux/INIT

HelloWorld

参考官方示例,定义一个计算器。该计算器只有 +- 操作,每次操作后的结果都将映射到 state

step1: 定义 state 对象结构

由于只有 +- 操作,所以 state 我们可以直接定义成一个 int 数据,state 即是操作后的结果,state 默认为0;

step2: 定义 action

action.js

1
2
3
4
5
6
7
8
9
10
11
12
export const INCREMENT = 'INCREMENT'; // +
export const DECREMENT = 'DECREMENT'; // -
export const increment = (num=1) => ({
type: INCREMENT,
num
});
export const decrement = (num=1) => ({
type: DECREMENT,
num
});

定义两个 action,以及对应的操作量 num,加多少和减多少;increment/decrement 就是两个不同的 action,有 type 和对应的数据;

step3: 定义 reducer

reducer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {INCREMENT, DECREMENT} from './action.js';
function counter(state=0, action) {
switch (action.type) {
case INCREMENT:
return state + action.num;
case DECREMENT:
return state - action.num;
default :
return state;
}
}
module.exports = counter;

定义了一个 counter 函数用于处理 actioncounter 第一个参数是 state,表示整个应用程序中的 state 数据对象,初始值是0,第二个参数 action 则是 step2 中的 action

step4: 使用 store 分发事件及监听

index.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
import React from 'react';
import {render} from 'react-dom';
import { createStore } from 'redux';
import reducer from './reducer.js';
import {increment, decrement} from './action.js';
// 生成 store 对象
var store = createStore(reducer);
// 定义 App 应用程序
const App = React.createClass({
add: function () {
store.dispatch(increment(3));
},
dec: function () {
store.dispatch(decrement(2));
},
render: function () {
return <div>
<button onClick={this.add}>ADD</button>
<span style={{'paddingLeft': '8px', 'paddingRight': '8px'}}>{store.getState()}</span>
<button onClick={this.dec}>DEC</button>
</div>
}
});
// 定义 rendering 函数
var rendering = ()=> {
render(<App/>, document.getElementById('root'));
};
// 添加订阅,当 state 改变时,调用 rendering 函数更新应用程序;
store.subscribe(rendering);
rendering();

Steps:

  • 使用 createStore 生成 store 对象;
  • 定义 App 应用程序对象,定义两个方法 add 和 dec,分别使用 store 分发 increment 事件和 decrement 事件,加减量分别是 3、2,两个按钮分别用于触发 add 和 dec 函数实现加减;
  • 定义 rendering 函数,将 App 组件渲染到浏览器;
  • 调用 store.subscribe 函数添加订阅,当 state 改变时,再次渲染页面;

webpack.config.js

由于 importreact的语法在现代浏览器中并不能很好的被支持,所以需要使用 webpack 结合babel将新的语法转换为es5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var path = require("path");
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
publicPath: '/',
chunkFilename: '[name].[chunkhash:5].chunk.js'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader?presets[]=es2015&presets[]=react'
}
]
}
}

依赖的 loader 及组件: react, react-dom, babel, babel-loader, babel-preset-es2015, babel-preset-react