Redux API 总览
浅谈redux 中间件的原理
在 Redux 的源码目录 src/
,我们可以看到如下文件结构:
├── utils/
│ ├── warning.js # 打酱油的,负责在控制台显示警告信息
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js # 入口文件
除去打酱油的 utils/warning.js 以及入口文件 index.js,剩下那 5 个就是 Redux 的 API
compose(...functions)
先说这个 API 的原因是它没有依赖,是一个纯函数
⊙ 源码分析
/**
* 看起来逼格很高,实际运用其实是这样子的:
* compose(f, g, h)(...arg) => f(g(h(...args)))
*
* 值得注意的是,它用到了 reduceRight,因此执行顺序是从右到左
*
* @param {多个函数,用逗号隔开}
* @return {函数}
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
---
compose(fn1,fn2,fn3)(0)
![屏幕快照 2018-08-05 上午8.03.37.png](https://upload-images.jianshu.io/upload_images/8805811-1d9dd6013da732f6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这里的关键点在于,reduceRight
可传入初始值:
// 由于 reduce / reduceRight 仅仅是方向的不同,因此下面用 reduce 说明即可
var arr = [1, 2, 3, 4, 5]
var re1 = arr.reduce(function(total, i) {
return total + i
})
console.log(re1) // 15
var re2 = arr.reduce(function(total, i) {
return total + i
}, 100) // <---------------传入一个初始值
console.log(re2) // 115
下面是 compose
的实例(在线演示):
<!DOCTYPE html>
<html>
<head>
<script src="//cdn.bootcss.com/redux/3.5.2/redux.min.js"></script>
</head>
<body>
<script>
function func1(num) {
console.log('func1 获得参数 ' + num);
return num + 1;
}
function func2(num) {
console.log('func2 获得参数 ' + num);
return num + 2;
}
function func3(num) {
console.log('func3 获得参数 ' + num);
return num + 3;
}
// 有点难看(如果函数名再长一点,那屏幕就不够宽了)
var re1 = func3(func2(func1(0)));
console.log('re1:' + re1);
console.log('===============');
// 很优雅
var re2 = Redux.compose(func3, func2, func1)(0);
console.log('re2:' + re2);
</script>
</body>
</html>
控制台输出:
func1 获得参数 0
func2 获得参数 1
func3 获得参数 3
re1:6
===============
func1 获得参数 0
func2 获得参数 1
func3 获得参数 3
re2:6
Store Enhancer
说白了,Store 增强器就是对生成的 store
API 进行改造,这是它与中间件最大的区别(中间件不修改 store
的 API)
而改造 store
的 API 就要从它的缔造者 createStore
入手。例如,Redux 的 API applyMiddleware
就是一个 Store 增强器。
为什么 applyMiddleware
要为 dispatch
创建一个闭包?
applyMiddleware
从 store 中获取已有的 dispatch,然后把它封装在一个闭包中来创建最开始的 middleware 链。然后用一个对象调用来调用,以暴露出 getState 和 dispatch 函数。这样做可以使得 middleware 在初始化时可以使用 dispatch。
import compose from './compose' // 这货的作用其实就是 compose(f, g, h)(action) => f(g(h(action)))
/* 传入一坨中间件 */
export default function applyMiddleware(...middlewares) {
/* 传入 createStore */
return function(createStore) {
/* 返回一个函数签名跟 createStore 一模一样的函数,亦即返回的是一个增强版的 createStore */
return function(reducer, preloadedState, enhancer) {
// 用原 createStore 先生成一个 store,其包含 getState / dispatch / subscribe / replaceReducer 四个 API
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch // 指向原 dispatch
var chain = [] // 存储中间件的数组
// 提供给中间件的 API(其实都是 store 的 API)
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 给中间件“装上” API,见上面 ⊙Middleware【降低逼格写法】的【锚点-1】
chain = middlewares.map(middleware => middleware(middlewareAPI))
//chain数组里面的数据结构是这样的 :
//[ function(next){return function(action){...}},function(next){return function(action){...}} ...]
// 串联所有中间件
// store.dispatch传给last(...args)
dispatch = compose(...chain)(store.dispatch)
// 例如,chain 为 [M3, M2, M1],而 compose 是从右到左进行“包裹”的
// 那么,M1 的 dispatch 参数为 store.dispatch(见【降低逼格写法】的【锚点-2】)
// 往后,M2 的 dispatch 参数为 M1 的中间件处理逻辑哥(见【降低逼格写法】的【锚点-3】)
// 同样,M3 的 dispatch 参数为 M2 的中间件处理逻辑哥
// 最后,我们得到串联后的中间件链:M3(M2(M1(store.dispatch)))
//(这种形式的串联类似于洋葱,可参考文末的拓展阅读:中间件的洋葱模型)
// 在此衷心感谢 @ibufu 在 issue8 中指出之前我对此处的错误解读
return {
...store, // store 的 API 中保留 getState / subsribe / replaceReducer
dispatch // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会触发 chain 内的中间件链式串联执行
}
}
}
}
最终返回的虽然还是 store
的那四个 API,但其中的 dispatch
函数的功能被增强了,这就是所谓的 Store Enhancer
Middleware
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch
方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
Middleware 最常见的使用场景是无需引用大量代码或依赖类似 Rx 的第三方库实现异步 actions。这种方式可以让你像 dispatch 一般的 actions 那样 dispatch 异步 actions。
例如,redux-thunk 支持 dispatch function,以此让 action creator 控制反转。被 dispatch 的 function 会接收 dispatch
作为参数,并且可以异步调用它。这类的 function 就称为 thunk。另一个 middleware 的示例是 redux-promise。它支持 dispatch 一个异步的 Promise action,并且在 Promise resolve 后可以 dispatch 一个普通的 action。
Middleware 并不需要和 createStore
绑在一起使用,也不是 Redux 架构的基础组成部分,但它带来的益处让我们认为有必要在 Redux 核心中包含对它的支持。因此,虽然不同的 middleware 可能在易用性和用法上有所不同,它仍被作为扩展 dispatch
的唯一标准的方式。
说白了,Redux 引入中间件机制,其实就是为了在 dispatch 前后,统一“做爱做的事”。。。
诸如统一的日志记录、引入 thunk 统一处理异步 Action Creator 等都属于中间件
下面是一个简单的打印动作前后 state 的中间件:
让 middleware 以方法参数的形式接收一个 next() 方法,而不是通过 store 的实例去获取。
/* 装逼写法 */
const printStateMiddleware = ({ getState }) => next => action => {
console.log('state before dispatch', getState())
let returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
-------------------------------------------------
/* 降低逼格写法 */
function printStateMiddleware(middlewareAPI) { // 记为【锚点-1】,中间件内可用的 API
return function (dispatch) { // 记为【锚点-2】,传入上级中间件处理逻辑(若无则为原 store.dispatch)
// 下面记为【锚点-3】,整个函数将会被传到下级中间件(如果有的话)作为它的 dispatch 参数
return function (action) { // <---------------------------------------------- 这货就叫做【中间件处理逻辑哥】吧
console.log('state before dispatch', middlewareAPI.getState())
var returnValue = dispatch(action) // 还记得吗,dispatch 的返回值其实还是 action
console.log('state after dispatch', middlewareAPI.getState())
return returnValue // 将 action 返回给上一个中间件(实际上可以返回任意值,或不返回)
// 在此衷心感谢 @zaleGZL 在 issue15 中指出之前我对此处的错误解读
}
}
}
======
// 简单写法,主要看流程
function middleware({dispatch, getState}) {
return function (next) {
return function (action) {
return next(action);
}
}
}
Middleware 接收了一个 next() 的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next(),以此类推。由于 store 中类似 getState() 的方法依旧非常有用,我们将 store 作为顶层的参数,使得它可以在所有 middleware 中被使用。
参数
-
...middleware
(arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受Store
的dispatch
和getState
函数作为命名参数,并返回一个函数。该函数会被传入 被称为next
的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用next(action)
,或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的dispatch
方法作为next
参数,并借此结束调用链。所以,middleware 的函数签名是({ getState, dispatch }) => next => action
。
返回值
(Function) 一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名是 createStore => createStore
,但是最简单的使用方法就是直接作为最后一个 enhancer
参数传递给 createStore()
函数。
示例: 自定义 Logger Middleware
import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
function logger({ getState }) {
return next => action => {
console.log('will dispatch', action)
// 调用 middleware 链中下一个 middleware 的 dispatch。
let returnValue = next(action)
console.log('state after dispatch', getState())
// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。
return returnValue
}
}
let store = createStore(
todos,
['Use Redux'],
applyMiddleware(logger)
)
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
// (将打印如下信息:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
整个流程梳理
简单版本
看以下代码就知道大体流程了。
说白了,Store 增强器就是对生成的 store API 进行改造,这是它与中间件最大的区别(中间件不修改 store 的 API)
而改造 store 的 API 就要从它的缔造者 createStore 入手。例如,Redux 的 API applyMiddleware 就是一个 Store 增强器:
export default function applyMiddleware(...middlewares) {
//返回一个函数签名跟 createStore 一模一样的函数
//亦即返回的是一个增强版的 createStore
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
// 提供给中间件的 API(其实都是 store 的 API)
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 给中间件“装上” API
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
// 例如,chain 为 [M3, M2, M1],而 compose 是从右到左进行“包裹”的
// 那么,M1 的 dispatch 参数为 store.dispatch(见【降低逼格写法】的【锚点-2】)
// 往后,M2 的 dispatch 参数为 M1 的中间件处理逻辑哥(见【降低逼格写法】的【锚点-3】)
// 同样,M3 的 dispatch 参数为 M2 的中间件处理逻辑哥
// 最后,我们得到串联后的中间件链:M3(M2(M1(store.dispatch)))
//(这种形式的串联类似于洋葱,可参考文末的拓展阅读:中间件的洋葱模型)
return {
...store,
dispatch
}
}
}
export function createStore (reducer,enhancer) {
// 如果传入了applymiddleweare,把createStore包装
if(enhancer){
return enhancer(createStore)(reducer)
}
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
//reducer只是返回对象,所以需要用每次 reducer(state, action) 的调用结果覆盖原来的 state
state = reducer(state, action)
listeners.forEach((listener) => listener())
return action
// 为了方便链式调用,dispatch 执行完毕后,返回 action
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
将 middleware 串连起来的必要性是显而易见的。
如果 applyMiddlewareByMonkeypatching 方法中没有在第一个 middleware 执行时立即替换掉 store.dispatch,那么 store.dispatch 将会一直指向原始的 dispatch 方法。也就是说,第二个 middleware 依旧会作用在原始的 dispatch 方法。
但是,还有另一种方式来实现这种链式调用的效果。可以让 middleware 以方法参数的形式接收一个 next() 方法,而不是通过 store 的实例去获取。
action:第三个参数备注一下
function readMsg(from){
// getState,获取redux内已经登录的信息
return (dispatch,getState)=>{
axios.post('/user/readmsg',{from}).
then(res=>{
// 获取当前用户ID
const userid = getState().user._id
if(res.status == 200 && res.data.code == 0){
dispatch(msgRead({userid,from,num:res.data.num}))
}
})
}
}
===================
const arrayThunk = ({dispatch,getState})=>next=>action=>{
// 如果是函数,执行以下,参数是dispatch和getState
//一下是多个中间件的情况
if(Array.isArray(action)){
action.forEach(v=>next(v))
}
if(typeof action == 'function'){
return action(dispatch,getState)
}
// 默认,什么都没干
return next(action)
}
=====
/* 降低逼格写法 */
function printStateMiddleware(middlewareAPI) { // 记为【锚点-1】,中间件内可用的 API
return function (dispatch) { // 记为【锚点-2】,传入上级中间件处理逻辑(若无则为原 store.dispatch)
// 下面记为【锚点-3】,整个函数将会被传到下级中间件(如果有的话)作为它的 dispatch 参数
return function (action) { // 这货就叫做【中间件处理逻辑哥】吧
console.log('state before dispatch', middlewareAPI.getState())
var returnValue = dispatch(action) // 还记得吗,dispatch 的返回值其实还是 action
console.log('state after dispatch', middlewareAPI.getState())
return returnValue // 将 action 返回给上一个中间件(实际上可以返回任意值,或不返回)
}
}
}
Redux 有五个 API,分别是:
- createStore(reducer, [initialState])
- combineReducers(reducers)
- applyMiddleware(...middlewares)
- bindActionCreators(actionCreators, dispatch)
- compose(...functions)
createStore 生成的 store 有四个 API,分别是:
- getState()
- dispatch(action)
- subscribe(listener)
- replaceReducer(nextReducer)