Redux 学习

一、认识 Redux

安装

npm install --save redux
npm install --save react-redux

一句话理解 Redux

应用中的所有state都以一个对象数树的方式存储在一个单一的store中,唯一改变state的方法就是触发action,一个描述发生什么的对象,为了描述action如何改变state树,需要编写reducers。

什么是 Action?

Action本质上是JavaScript的普通对象,action内必须使用一个字符串类型的type字段来表示将要执行的动作。多数情况下type会被定义成字符串常量。我们应该尽量减少在actin中传递数据。
1
2
3
4
5
6
7
8
9
10
Action
export const ADD = 'ADD'
export const add = () => ({
type: ADD
})

export const SUB = 'SUB'
export const sub = () => ({
type: SUB
})

什么是 Reducer?

  1. 设计 state 结构
  2. Action 处理

reducer 是一个纯函数,接受旧的 state 和 action,返回新的 state。

注意: 每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
Reducer
import { ADD, SUB } from 'Action'

const counter = (state = 0, action) => {
switch (action.type) {
case ADD:
return state + 1
case SUB:
return state - 1
default:
return state
}
}

总结:把要做的修改变成一个普通对象,这个对象叫做 action,而不是直接修改 state。然后编写专门的函数来决定每个 action 如何改变应用的 state,这个函数叫做 reducer。


二、关于 Redux 的重点知识

1. 三大原则

(1)单一数据源

整个应用的state被存储在一棵object tree中,并且这个object tree只存在于唯一一个store中。

(2)state 是只读的

唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象。

(3)使用纯函数来执行修改

为了描述action如何改变state,需要写reducers。

注意:永远不要在 reducer 中做这些操作

  1. 修改传入参数;
  2. 执行有副作用的操作,如 API 请求和路由跳转;
  3. 调用非纯函数,如 Date.now(), Math.random()。

只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况,没有副作用,没有 API 请求,没有变量修改,单纯执行计算。

2.combineReducer

为什么使用 combineReducer?

随着应用变得复杂,需要对 reducer 函数进行拆分,拆分后的每一块独立负责管理 state 的一部分。

combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。

合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。
1
2
3
4
5
6
7
8
import { combineReducer } from "redux";

const todoApp = combineReducer({
visibilityFilter,
todos,
});

export default todoApp;

等价写法 =>

1
2
3
4
5
6
export const todoApp = (state = {}, action) => {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
todos: todos(state.todos, action)
}
}

conbineReducer 所做的是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据他的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。

combineReducer 示例

reducers/todos.js
1
2
3
4
5
6
7
8
export default function todos(state = [], action) {
switch (action.type) {
case "ADD_TODO":
return state.concat([action.text]);
default:
return state;
}
}
reducers/counter.js
1
2
3
4
5
6
7
8
9
10
export default function counter(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
reducers/index.js
1
2
3
4
5
6
7
8
import { combineReducers } from "redux";
import todos from "./todos";
import counter from "./counter";

export default combineReducers({
todos,
counter,
});
App.js
1
2
3
4
import { createStore } from "redux";
import reducer from "./reducers/index";

let store = createStore(reducer);

3.Redux 中的 connect()方法

(1)定义 mapStateToProps 方法

把当前Redux store state映射到展示组件的props中。

(2)定义 mapDispatchToProps 方法

接收dispatch()方法并返回期望注入到展示组件的props中的回调方法。
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
import { connect } from 'react-redux'
import { Task } from './reducers'

const mapStateToProps = (state) => {
return {
tasks: state.entities.tasks
}
}

const mapDispatchToProps = (dispatch) => {
return {
addUnit: (taskID, unitID, units) => {
dispatch(Task.addUnit(taskID, unitID, units))
}
}
}

等价于 =>

const mapDispatchToProps = (dispatch) => {
return {
addUnit: (taskID, unitID, units) => Task.addUnit(taskID, unitID, units))
}
}


//多个conncet的书写方式
const VisibleCreateUnit = connect(
mapStateToProps,
mapDispatchToProps
)(CreateUnit)

const VisibleCreateTask = connect(
mapStateToProps,
mapDispatchToProps
)(CreateTask)

export { VisibleCreateUnit, VisibleCreateTask }

4.Store

(1)createStore()

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。

(2)Redux Provider

1
2
import { Provider } from "react-redux";
import { createStore } from "redux";
在 React-router 中使用 Provider
1
2
3
4
5
6
7
const store = createStore(appReducer, normalizedState)

<Provider store={store}>
<Router history={browserHistory}>
<Route path="/app" component={App} />
</Router>
</Provider>

5.数据序列化(normalize)

示例:Tasks中包含subTasks和units,subTasks中又包含units。
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
import { normalize, schema } from 'normalizr'

//首先使用schema最小单元即units
const unitsSchema = new schema.Entity('units')
//subTasks中包含units
const subTasksSchema = new schema.Entity('subTasks', { units: [unitsSchema] })
//tasks中包含subTasks和units
const tasksSchema = new schema.Entity('tasks', {
subTasks: [subTasksSchema],
units: [unitsSchema]
})


......
const initialState = {
tasks: {
subTasks: {
units: {
......
}
......
},
units: {
......
}
},
groups: {
......
},
classes: {
......
},
rosters: {
......
}
}

const stateSchema = {
tasks: [tasksSchema],
groups: [groupsSchema],
classes: [classesSchema],
rosters: [rostersSchema]
}

const normalizedState = normalize(initialState, stateSchema)
console.log('normalizedState', normalizedState)

Object{
entities: Object{
.....
}
result: Object{
tasks: array[2],
groups: array[3],
classes: array[4],
rosters: array[5]
}
}

注意:键一定与数组中的键一一对应

Redux 工作流程

1、用户发出 Action

1
store.dispatch(action);

2、Store 自动调用 reducer,并且传入两个参数: 当前 state, action。Reducer 返回新的 State

1
let nextState = reducer(previousState, action);

3、State 一但有变化,store 就会调用监听函数

1
2
3
4
5
store.subscribe(listener);
function listener() {
let newState = store.getState();
component.setState(newState);
}