一、认识 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?
- 设计 state 结构
- 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 中做这些操作
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 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)) } }
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'
const unitsSchema = new schema.Entity('units')
const subTasksSchema = new schema.Entity('subTasks', { units: [unitsSchema] })
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
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); }
|