let next = store.dispatch
store.dispatch = function dispatchWithLog(action) {
console.log('dispatching', action)
next(action)
console.log('next state', store.getState())
}
到此,每次触发一个action都会有记录
尝试将dispatch增强函数封装起来
function enhanceDispatchWithLog(store) {
let next = store.dispatch
return function dispatchWithLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
// 改变dispatch的行为
const dispatchWithLog = enhanceDispatchWithLog(store)
// 触发一个action
dispatchWithLog(changeNum(2))
const callback = () => { //do somethine... }
function enhanceDispatchWithCallback(store) {
let next = store.dispatch
return function dispatchWithCallback(action) {
let result = next(action)
callback()
return result
}
}
const dispatchWithCallback = enhanceDispatchWithCallback(store)
dispatchWithCallback(changeNum(2))
动机
JavaScript
需要管理比任何时候都要多的state (状态)
,包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。基本思想
{num: 1}
{ type: 'CHANGE_NUM', result: 2 }
(oldState, action) => newState
——({num: 1}, { type: 'CHANGE_NUM', result: 2 }) => {num: 2}
三个原则
state
被储存在一棵object tree
中,并且这个object tree
只存在于唯一一个store
中。state
的方法就是触发action
,action
是一个用于描述已发生事件的普通对象。action
如何改变state tree
,你需要编写纯函数reducers
来执行修改。基础
Action
Action是把数据从应用传到
store
的有效载荷。它是store
数据的唯一来源。一般来说你会通过store.dispatch()
将action
传到store
。action
本质上是JavaScript
普通对象。我们约定,action
内必须使用一个字符串类型的type
字段来表示将要执行的动作。多数情况下,type
会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放action
。Action 创建函数
在
Redux
中的action
创建函数只是简单的返回一个action
:这样做将使
action
创建函数更容易被移植和测试。Reducer
Action
只是描述了有事情发生了这一事实,并没有指明应用如何更新state
。而这正是reducer
要做的事情。reducer
就是一个纯函数,接收旧的state
和action
,返回新的state
。state
范式化:开发复杂的应用时,不可避免会有一些数据相互引用。建议你尽可能地把state
范式化。把所有数据放到一个对象里,每个数据以ID为主键,不同实体或列表间通过 ID 相互引用数据。把应用的 state 想像成数据库。这种方法在 normalizr 文档里有详细阐述。保持
reducer
纯净非常重要。永远不要在reducer
里做这些操作:Date.now()
或Math.random()
。不要修改
state
。 使用Object.assign()
新建了一个副本。不能这样使用Object.assign(state, { num: action.result })
,因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7
提案对象展开运算符
的支持, 从而使用{ ...state, ...newState }
达到相同的目的。在
default
情况下返回旧的state
。遇到未知的action
时,一定要返回旧的state
。如果状态很庞大,可以拆分多个子
reducer
,每个reducer
只负责管理全局state
中它负责的一部分。每个reducer
的state
参数都不同,分别对应它管理的那部分state
数据最后所有的子
reducer
可以用combineReducers()
合并,combineReducers
接收一个对象,生成一个函数,这个函数来调用你的一系列reducer
,每个reducer
根据它们的key
来筛选出state
中的一部分数据并处理,然后这个生成的函数再将所有reducer
的结果合并成一个大的对象。Store
Store
有以下职责:state
;getState()
方法获取 state;dispatch(action)
方法更新 state;subscribe(listener)
注册监听器;subscribe(listener)
返回的函数注销监听器。根据已有的 reducer 来创建 store :
let store = createStore(app)
完整例子:
Redux & React
React
更多的是view
层,每个组件本身是通过props
和state
的更新来更新组件。state
是各个组件内部的属性,外部无法较好的访问和控制。所以Redux
对React App
状态的控制是通过props
完成的。将store注入组件
使用指定的
react-redux
组件<Provider>
可以让所有组件都可以访问store
,而不必显式地传递它。因为
store
的state
是整个App
的,当前组件并不一定都需要。不显式传递的好处是,组件可以需要什么state
,就拿什么。根组件想获取到
store
,可使用react-redux
的connect()
方法来生成。connect()
前,需要先定义mapStateToProps
这个函数来指定如何把当前state
映射到展示组件的props
中。mapDispatchToProps()
方法接收dispatch()
方法并返回期望注入到展示组件的props
中的回调方法connect()
传入这两个函数。一般只需要在渲染根组件时使用即可,这样做可以保证数据流从上至下的一致性。
高级
异步Action
异步 API
时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻(也可能是超时)。dispatch
一个action
返回普通对象时,state
会立即改变,所以在使用异步api
时,我们需要去写异步dispatch
流程。同步action
一样去调用action
生成函数,将异步的操作写在action生成函数
中。redux-thunk
,action 创建函数
除了返回action 对象
外还可以返回函数。这时,这个action 创建函数
就成为了thunk
。dispatch
某个thunk函数
时,这个函数会被Redux Thunk middleware
执行。异步 API 请求
。这个函数还可以dispatch action
,就像dispatch
前面定义的同步 action
一样。Middleware
Redux middleware
提供的是位于action
被发起之后,到达reducer
之前的扩展点。 你可以利用Redux middleware
来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。通过一个例子理解
Middleware
记录日志:记录每次触发的action
以及state变化
。到此,每次触发一个action都会有记录
到此,完成了一个dispatch的改造函数。此时出现了另一个问题,我们还想给dispatch添加其他功能,应该怎么办呢
比如我们想在dispatch函数里,添加自定义的callback,以便我们在每次触发action时,可以调用callback函数。
到此我们又改造了一个dispatch函数。但是上述的改造是一个替换过程,即连续多个dispatch的改造只有最后一个会生效。因此新的需求又来了 —— 如何让dispatch函数同时具有以上两个增强功能呢
想要实现这个需求,我们需要将每次
dispatch增强函数
改造后的dispatch(在此称为next)
,传进下一个改造函数,使得每次增强前,dispatch
都已经具备了之前增强功能为了保证只能应用 一次
middleware
,applyMiddleware
将作用在createStore()
上而不是 store 本身。因此他的输入输出由(store, middlewares) => { return store }
, 改为(...middlewares) => createStore => { return createStore }