前言
目前公司是基于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源码的简单解析,大致理解了它本身的内部机制, 现在再到头部看那张流程图是否理解得更加透彻了呢