前言
目前公司是基于React
的技术栈,而我自己对于React
的理解只限于应付项目业务开发而已,而且公司项目基本都是依赖umi
框架机制,导致新人甚至对一些最基本的知识点都不了解就开始着手开发,比如react-router
/react-redux
大佬造的轮子用多了也是会有负面影响,为了不让自己做一条只会用别人东西的咸鱼,决定去了解下redux
的原理,因为redux
源码相对比较简洁而且通过redux
大致都能了解其他的状态管理工具的设计思路,一通则百通,无外乎这个道理,以后就算有几百个轮子都可以很快理解
工作流程图
在了解源码之前先可以仔细看下Redux
的整体流程图(感谢若川大佬的图,这里冒昧借用下),如果能简单看懂这张图,那么redux
的源码学习将会非常快速

调试
为了了解源码,就必须要学会调试代码,现在我们去 redux@4.0.4 拷贝redux
的代码到本地
git clone https://github.com/reduxjs/redux.git
可以很清晰的看到redux
是使用rollup
打包,为了能搞更好地调试,给它加上sourcemap
配置
//packages.json
"scripts": {
"build": "rollup -c -m",
...
}
执行yarn build
命令,可以看到生成了es
/dist
/lib
对应不同的环境
接下来我是直接使用的官方examples
文件夹里的async
例子,直接把es
文件复制进去,在examples/async/index.js
里更改引入
// import { createStore, applyMiddleware } from 'redux'
import { createStore, applyMiddleware } from './es/redux.js'
源码理解
很多文章都是从createStore
开始介绍,我按照自己的思路,从combineReducers
开始一步一步理解redux
的机制
combineReducers
先看下一个redux
官方给出的简单例子: /reducesc/index.js

这里定义了postsBySubreddit
,selectedSubreddit
两个函数,即为两个reducer
, 也是我们项目中都会存在的,而且只会比它多。而在index.js
中,需要把整合的rducer
传入createStore
中,下面我们看看 combineReducers
对这两个reducer
做了什么

可以很清晰得看到combineReducers
获取两个reducer
函数,经过简单地校验,生成了类似{ [function.name]: function }
的键值对象, 并存放在闭包中(finalReducers
), 最后返回了一个combination
函数,用来传入createStore
中作为形参,而combination
具体做了什么下面会讲
createStore
当reducer
作为参数传入createStore
中之后,createStore
函数执行返回核心的store
对象,顺着思路来看下createStore
中做了什么

store
就是返回dispatch
、subscribe
、getState
、replaceReducer
等方法。工作中用到过redux
的应该非常熟悉。
现在我们具体看下对传进来的reducer
做了什么,其实上面都是一些方法的定义,重点要注意这句
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
这里单独执行一个distpatch
方法,是为了初始化生成一个state tree
, 也就是生成一个包含store
里面所有state
的树形结构对象,接下来主要看下dispatch
做了什么, 而我们之前传入的reducer
也就在这里起了作用
dispatch

这里我们需要关注的重点就是currentReducer
(其实就是上文传过来的combination
函数),这里调用了这个函数,并传入state
/action

这里可以看出combination
拿到最初的state
和action
, 先做了简单的判断,之后也就是这里的一个问题:遍历执行所有的reducer
来改变state
当然,最初的时候是形成了基本的树形结构state
, 但是之后的每一步dispatch
都要去遍历所有的reducer
得到新的state
进行比较再返回最终的state
, 保存进createStore
里的currentState
中,可以通过store
暴露的getState
方法获取当前的state
subscribe
subscribe
其实就是订阅发布模式
的一种实现吧,我是这么理解的,每当 dispatch action
的时候就会执行,state
树中的一部分可能已经变化。你可以在回调函数里调用 getState()
来拿到当前 state
。它的代码也很简单
我们在index.js
中加入,然后开始调试,它会走到createStore
中暴露的subscribe
方法里
debugger;
store.subscribe(() => console.log(7))

subscribe
方法会把listener
加入到nextListeners
中,相当于发布订阅模式
中的消息队列
,之后在dispatch
函数执行的时候,遍历nextListeners
出发listener
函数,具体代码在dispatch
中查看,这里不做详述了
以上是对基本的redux
机制做了简单的分析理解,我们会发现这些功能还不足以完全承载我们的业务,所以redux
本身引入中间件
的概念对本身功能进行扩展。社区有很多中间件
的开源库,包括redux-thunk
,redux-promise
,redux-saga
等等等等,学是学不完的,但还是那句话,一通百通的道理......
中间件机制
applyMiddleware
如果我们要加入中间件,可以在createStore
中加入参数applyMiddleware(...middlewares)
, 这里我以redux-thunk
为例
import { createStore, applyMiddleware } from './es/redux.js'
import thunk from 'redux-thunk'
import reducer from './reducers'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
看一下applyMiddleware
函数具体做了什么

这里applyMiddleware
用了柯里化
的理念,按上面的例子来说第一个参数就是thunk
,获取数据之后会在createStore
里调用
接下来看下createStore
中对于中间件的处理逻辑

这里的enhancer
函数就是上方的applyMiddleware(thunk)
返回函数,再次传入createStore
以及初始化的reducer
和preloadedState
, 这表示又再次走到了applyMiddleware
函数里面

这里跟上面一样生成了store
对象,赋给middleware
返回chain
,这里最难理解的就是compose
方法,也是中间件机制的关键所在
compose

这里其实也是函数式编程
的一个组合
的理念,把函数从右至左进行组合,可以理解为上个函数的返回就是下个函数的参数,如果还是不明白这个reduce
代码可能不理解,那我们慢慢来理解它
首先先看个例子:

很明显结果是108
, 执行顺序依次是multiply
,add
,minus
,那接下来我们把它写成通用的函数

redux
中的compose
跟这很类似,但是区别在于它返回的是双层函数,即next(...)
函数,这里是中间件机制最难理解的地方,它就是通过这种方式来实现洋葱模型的
为了理解compose
的组合机制,我们先回来看下react-thunk
的代码

接来下我们再自己实现一个简单的middleware

在看下上面的调试的截图,多个中间件时,chain
则等于[next => action => ..., next => action => ...]
, 再进入compose
函数,得到的结构就是(...args) => a(b(...args))
, 这时又传入store.dispatch
(即next
函数)
- 再观察下这两个中间件,
store.dispatch
传入之后,next
被替代为store.dispatch
而返回了action => {}
函数,这个函数又作为下一个中间件的next
函数 - 当用户执行
dispatch(action)
, 中间件接收到了action
,则开始调用刚传入的next
函数指向外部next
直至store.dispatch
现在我们根据redux-thunk
具体的例子来理解:
export const requestPosts = subreddit => ({
type: REQUEST_POSTS,
subreddit
})
const fetchPosts = subreddit => (dispatch, getState) => {
dispatch(requestPosts(subreddit))
}
// dispatch action in redux-thunk
dispatch(fetchPosts(subreddit))
-
根据
react-thunk
的代码,fetchPosts(subreddit)
的返回值就是action
, 而action
是函数, 则return action(dispatch, getState, extraArgument);
, 根据例子上的第二个返回函数(dispatch, getState) => {}
由此而来 -
这里的
action(dispatch, ...)
中的dispatch
则是compose
组合返回的,requestPosts(subreddit)
执行后再次进入react-thunk
中间件,由于不是函数则执行next(action)
,这时候如果有其他的中间件,则根据compose
继续向左执行
总结
通过以上对Redux
源码的简单解析,大致理解了它本身的内部机制, 现在再到头部看那张流程图是否理解得更加透彻了呢