第一次看源码,并没有想象中的难哈,主要是redux的源码比较少,理解起来也比较简单。看过之后,感觉更深入的理解了redux思想和函数式编程的理念,建议大家可以去看一下嘻嘻,看完之后肯定会有收获的。
我是对照着网上别人看过的源码笔记看的,写这篇文章的原因呢,是想总结一下,因为我记性比较差啦,算是做一个笔记,方便以后复习。
redux的源码中,有6个js文件,分别是:
- index.js
- createStore.js
- combineReducers.js
- bindActionCreators.js
- compose.js
- applyMiddleware.js
我们一个一个来分析吧~
index
这里呢没有太多需要讲的,就是暴露了5个核心的api,分别是:
- createStore:接收state和reducer,生成一颗状态树store
- combineReducers:把子reducer合并成一个大reducer
- bindActionCreators:把actionCreators和dispatch封装成一个函数,也就是把他们两绑定在一起
- applyMiddleware:这是一个中间件
- compose:一个组合函数
createStore
首先,定义初始化的action
export const ActionTypes = {
INIT: '@@redux/INIT'
}
这个createStore函数,会传入三个参数和一个返回值,分别是:
1、 @param {Function} reducer
这个reducer是一个函数,这个函数接收state和action,作一系列计算之后返回一个新的state。这里就体现了函数式编程的一些特性:
第一,这个reducer是一个纯函数,纯函数的特点是:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。不理解纯函数的筒子们,可以上网搜索一下。
第二,state是不可变的,我们这里对state作的一些修改和计算,不是直接修改原来的数据,而是返回修改之后的数据,原来的数据是保持不变。这里可以衍生到immutable,可以使用immutable和redux搭配使用。
2、@param {any} [preloadedState]
这是初始化的state,不多说。
3、@param {Function} [enhancer]
这个enhancer其实就是一个中间件,它在redux3.1.0之后才加入的。相当于把store做一些增强处理,让store更强大,功能更丰富,在之后的applyMiddleware那里会详细说的。这里也体现了高阶函数的思想,就像react-redux的connect方法一样,做一些包装处理之后,再返回。
4、@returns {Store}
这是返回的值,返回的是一棵状态树,也就是store啦。
这是做的源码分析,都写在注释里了。createStore返回的最常用的三个api是dispatch,subscribe,getState,一般我们只要传入reducer和preloadedState,就可以直接调用这三个方法,非常方便。
export default function createStore(reducer, preloadedState, enhancer) {
//这里是一些参数校验
//如果第二个参数为函数且没有传入第三个参数,那就交换第二个参数和第三个参数
//意思是createSotre会认为你忽略了preloadedState,而传入了一个enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
//如果传入了第三个参数,但不是一个函数,就报错
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
//这是一个高阶函数调用方法。这里的enhancer就是applyMiddleware(...middlewares)
//enhancer接受createStore作为参数,对createStore的能力进行增强,并返回增强后的createStore
//然后再将reducer和preloadedState作为参数传给增强后的createStore,得到最终生成的store
return enhancer(createStore)(reducer, preloadedState)
}
//reducer不是函数,报错
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
//声明一些变量
let currentReducer = reducer //当前的reducer函数
let currentState = preloadedState//当前的状态树
let currentListeners = [] // 当前的监听器列表
let nextListeners = currentListeners //更新后的监听器列表
let isDispatching = false //是否正在dispatch
//判断当前listener和更新后的listener是不是同一个引用,如果是的话对当前listener进行一个拷贝,防止在操作新的listener列表的时候对正在发生的业务逻辑造成影响
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
*
* @returns {any} The current state tree of your application.
*/
//返回当前的状态树
function getState() {
return currentState
}
/**
*这个函数是给store添加监听函数,把listener作为一个参数传入,
*注册监听这个函数之后,subscribe方法会返回一个unsubscribe()方法,来注销刚才添加的监听函数
* @param {Function} listener 传入一个监听器函数
* @returns {Function}
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
//注册监听
let isSubscribed = true
ensureCanMutateNextListeners()
//将监听器压进nextListeners队列中
nextListeners.push(listener)
//注册监听之后,要返回一个取消监听的函数
return function unsubscribe() {
//如果已经取消监听了,就返回
if (!isSubscribed) {
return
}
//取消监听
isSubscribed = false
//在nextListeners中找到这个监听器,并且删除
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/**
* @param {Object} action 传入一个action对象
*
* @returns {Object}
*/
function dispatch(action) {
//校验action是否为一个原生js对象
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
//校验action是否包含type对象
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
//判断是否正在派发,主要是避免派发死循环
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
//设置正在派发的标志位,然后将当前的state和action传给当前的reducer,用于生成新的state
//这就是reducer的工作过程,纯函数接受state和action,再返回一个新的state
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
//得到新的state之后,遍历当前的监听列表,依次调用所有的监听函数,通知状态的变更
//这里没有把最新的状态作为参数传给监听函数,是因为可以直接调用store.getState()方法拿到最新的状态
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
//返回action
return action
}
/**
*这个方法主要用于reducer的热替换
* @param {Function} nextReducer
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 把传入的nextReducer给当前的reducer
currentReducer = nextReducer
//dispatch一个初始action
dispatch({ type: ActionTypes.INIT })
}
/**
* 用于提供观察者模式的操作,貌似是一个预留的方法,暂时没看到有啥用
* @returns {observable} A minimal observable of state changes.
*/
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer
* 观察者应该有next方法
* @returns {subscription}
*/
subscribe(observer) {
//观察者模式的链式结构,传入当前的state
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
//获取观察者的状态
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
//返回一个取消订阅的方法
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
//初始化一个action
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
combineReducers
combineReducers的作用是将之前切分的多个子reducer合并成一个大的reducer,也就是说将很多的小状态树合并到一棵树上,整合成一棵完整的状态树。
这个函数接受一个参数,返回一个函数
1、@param {Object} reducers
这里接收多个reducer,传入的是一个对象
2、@returns {Function}
combineReducers的整个执行过程就是:将所有符合标准的reducer放进一个对象中,当dispatch一个action的时候,就遍历每个reducer,来计算出每个reducer的state值。同时,每遍历一个reducer,就判断新旧state是否发生改变,来决定是返回新state还是旧state,这是做的一个优化处理。
源码分析如下,前面还有一部分是一些error信息和warning信息的处理,就没有放进来了,感兴趣的话可以自己去看一下完整的源码。
export default function combineReducers(reducers) {
//获取reducers的所有key值
const reducerKeys = Object.keys(reducers)
//最终生成的reducer对象
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
//遍历reducer,把key值都是function的reducer放进finalReducers对象中
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
//得到finalReducers的key值数组
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
//检测这些reducer是否符合标准
let shapeAssertionError
try {
//检测是否是redux规定的reducer形式
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
//计算state的逻辑部分
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
//如果不是production(线上)环境,做一些警告
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
//标志state是否改变
let hasChanged = false
//存储新的state
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
//遍历finalReducerKeys的key值,也就是reducer的名字
const key = finalReducerKeys[i]
//得到reducer的vlaue值
const reducer = finalReducers[key]
//变化前的state值
const previousStateForKey = state[key]
//变化后的state值,把变化前的state和action传进去,计算出新的state
const nextStateForKey = reducer(previousStateForKey, action)
//如果没有返回新的reducer,就抛出异常
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
//把变化后的state存入nextState数组中
nextState[key] = nextStateForKey
//判断state是否有改变
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//如果改变了state就返回新的state,没改变就返回原来的state
return hasChanged ? nextState : state
}
}
bindActionCreators
bindActionCreators的作用是:将action与dispatch函数绑定,生成可以直接触发action的函数。
//使用dispatch包装actionCreator方法
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
/*
* @param {Function|Object} actionCreators
*
* @param {Function} dispatch
*
* @returns {Function|Object}
*
*/
export default function bindActionCreators(actionCreators, dispatch) {
//actionCreators为函数,就直接调用bindActionCreator进行包装
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
//以下是actionCreators为对象时的操作
//遍历actionCreators对象的key值
const keys = Object.keys(actionCreators)
//存储dispatch和actionCreator绑定之后的集合
const boundActionCreators = {}
//遍历每一个对象,一一进行绑定
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
compose
compose叫做函数组合,是一个柯里化函数,将多个函数合并成一个函数,从右到左执行。这同时也是函数式编程的特性。这个函数会在applyMiddleware中用到
/**
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
export default function compose(...funcs) {
//判断传入参数的数量,做不同的处理
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
这一段代码的主要难点是在最后那一句,着重说一下reduce这个方法。这个reduce不是之前的reducer,这里的reduce函数是es5的一个归并数组的方法,是从数组的第一项开始,逐个遍历数组的所有项。
它接收两个参数,一个是在每一项上调用的函数,还有一个可选参数,是作为归并基础的初始值。调用的那个函数又接收四个参数,前一个值,当前值,项的索引,和数组对象。这个函数返回的任何值都会作为第一个参数自动传递给下一项。这样说可能比较抽象,举个例子:
[1,2,3,4,5].reduce((prev, cur) => {
return prev + cur //输出15
})
用reduce就可以很快的求的数组所有值相加的和。另外,还有一个reduceRight()方法,跟reduce是一样的,只不过是从数组的右边开始遍历的。
我们回到源码上面return funcs.reduce((a, b) => (...args) => a(b(...args)))
,这其实就是遍历传入的参数数组(函数),将这些函数合并成一个函数,从右到左的执行。这就是中间件的创造过程,把store用一个函数包装之后,又用另一个函数包装,就形成了这种包菜式的函数。
applyMiddleware
applyMiddleware是用来扩展redux功能的,主要就是扩展store.dispatch的功能,像logger、redux-thunk就是一些中间件。
它的实现过程是:在dispatch的时候,会按照在applyMiddleware时传入的中间件顺序,依次执行。最后返回一个经过许多中间件包装之后的store.dispatch方法。
如果理解了之前说的compose函数,这一段代码应该也很容易就能看懂啦。
/**
* @param {...Function} middlewares 接收不定数量的中间件函数
* @returns {Function} 返回一个经过中间件包装之后的store
*/
export default function applyMiddleware(...middlewares) {
//返回一个参数为createStore的匿名函数
return (createStore) => (reducer, preloadedState, enhancer) => {
//生成store
const store = createStore(reducer, preloadedState, enhancer)
//得到dispatch方法
let dispatch = store.dispatch
//定义中间件的chain
let chain = []
//在中间件中要用到的两个方法
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
//把这两个api给中间件包装一次
chain = middlewares.map(middleware => middleware(middlewareAPI))
//链式调用每一个中间件,给dispatch进行封装,再返回最后包装之后的dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
总结
整个的源码就全部分析完了,我们可以看到,redux的源码很多地方都体现了函数式编程的思想。函数式编程写出来的代码确实很漂亮很简洁,但是理解起来也比较困难。这也只是函数式编程的很小一部分,有兴趣的话可以去了解一下其他的部分。
写到这里也差不多了,希望以后有机会能多看点源码,get一些新的知识,最后感谢宋老师的宝贵意见,bye
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。