背景
因为就得去实习了。所以打算开始补补坑。比如自己阅读源码的计划。所以今天来聊聊的源码。后续会有和的源码阅读。搞定这些的话,就开始阅读一个node的库的源码了,比如和。
开始
-
总览, redux的文件结构
文件看起来貌似不少,其实,要理解redux的内部实现,主要就看 createStore.js
,applyMiddleware.js ,combineReducers.js和compose.js。下面从createStore.js开始看。
-
createStore.js
export default function createStore(reducer, preloadedState, enhancer) { // 如果第二个参数没有传入初始的state,而是传入了enhancer(为applyMiddleware调用的返回值), 那就将第二个参数,即preloadedState赋值给enhancer if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // 如果传入了enhancer,但enhancer不是一个函数,报错 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } // 反之, 执行。注意此处。此处意味着,如果createStore的时候传入了enhancer,是会将createStore传入enhancer中,执行enhancer, 而enhancer的返回值也是一个函数。具体的可以等到下面我们讲解applyMiddleware,看完你就知道到底发生了什么。 return enhancer(createStore)(reducer, preloadedState) } // 如果没传入enhancer,就继续下面的逻辑 // reducer是要求为一个函数的,如果不是一个函数,报错 if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } ..... ..... // 最后createStore就会返回dispatch,subscribe, getState等几个常用的api return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }; }复制代码
上面的代码给大家展览了下createStore这个函数大概做了什么,其实就是封装了一些api,最后暴露给用户使用。接下来看一下各个api的实现:
先看一下私有变量的定义
let currentReducer = reducer // 就是reducer嘛 let currentState = preloadedState // 就是传入的初始state嘛 let currentListeners = [] // 当前的监听器队列 let nextListeners = currentListeners // 未来的监听器队列 let isDispatching = false // 标志是否正在dispatch复制代码
getState : 用来获取store中的state的。因为redux是不允许用户直接操作state,对于state的获取,是得通过getState的api来获取store内部的state。
function getState() { // 如果正在dispatch的话, 说明新的state正在计算中,现在的state是旧的,为了确保用户能获得新的 // state,所以要加一个判断,如果是正在dispatch的话,就报错,反之,返回现在的state if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) } return currentState }复制代码
subscribe :redux提供了用户一个监听state变化的api,这个尤为重要,如果没有这个api的暴露,react-redux应该就比较实现了。
function subscribe(listener) { // listener是state变化时的回调,必须是个函数 if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } // 如果是正在dispatch中,就报错。因为要确保state变化时,监听器的队列也必须是最新的。所以监听器的注册要在计算新的state之前。 if (isDispatching) { throw new Error( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } // 标志是否注册,额,其实个人感觉没啥必要。不过仔细想想,应该是防止用户多次调用取消监听的函数。 let isSubscribed = true // 其实这个函数就是判断当前的监听器队列和未来的是否一样,如果不一样那就将当前的赋值给未来的,额,还是不是很理解为什么得这么实现,可能是为了达到数据不可变的效果,避免压进新的回调时,导致当前的监听器队列也有这个回调 ensureCanMutateNextListeners() // 将回调压进未来的监听器队列中 nextListeners.push(listener) // 注册监听器后会返回一个取消监听的函数 return function unsubscribe() { // 如果是已经调用该函数取消监听了,就返回 if (!isSubscribed) { return } if (isDispatching) { throw new Error( 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } // 标志已经取消了 isSubscribed = false ensureCanMutateNextListeners() // 删除 const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }复制代码
dispatch : 该函数是与getState对应的,getState是读,那dispatch就是写。redux的改动state,只能通过发起一个dispatch,传达一个action给reducer,reducer会根据action和currentState以及自己的内部实现逻辑,来计算出新的state,从而达到写的目的。
function dispatch(action) { // action要求是一个简单对象,而一个简单对象就是指通过对象字面量和new Object()创建的对象,如果不是就报错。 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } // reducer内部是根据action的type属性来switch-case,决定用什么逻辑来计算state的,所以type属性是必须的。 if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } // 如果是已经在dispatch的,就报错,避免不一致 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } // 这里就是计算新的state,并赋值给currentState try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } // state更新了后,就如之前我们所说的subscribe,将注册的回调都触发一遍。大家要注意这里,是都触发一遍哦!这个点了解,react-redux的一些原理会比较容易理解。 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }复制代码
以上就是createStore的大致实现。这个函数难度不大,更多的是一个了解redux的入口。咱们从这个入口来一点点挖掘别的代码的实现。下面先从combineReducers开始
-
combineReducers
- 这个函数是用来整合多个reducers的, 因为createStore只接受一个reducer。
- 这个函数分为两部分,第一部分是检验用户传入的reducers的准确性。第二部分就是计算state的逻辑。第一部分大家看一看也就了解了为什么,咱们主要看看第二部分
export default function combineReducers(reducers) { ........ ........ return function combination(state = {}, action) { if (shapeAssertionError) { throw shapeAssertionError } if (process.env.NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage( state, finalReducers, action, unexpectedKeyCache ) if (warningMessage) { warning(warningMessage) } } // hasChanged来标志是否计算出了新的state let hasChanged = false // 这个就是存储新的state的 const nextState = {} // emmmm, 就是遍历每一个reducer,把action传进去,计算state for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) // 如果某个reducer没有返回新的state,就报错 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey // 此处来判断是否有新的state hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 根据该标志,决定返回原来的state, 还是新的state return hasChanged ? nextState : state }}复制代码
这个整合的过程就是将所有的reducer存在一个对象里。当dispatch一个action的时候,通过遍历每一个reducer, 来计算出每个reducer的state, 其中用到的优化就是每遍历一个reducer就会判断新旧的state是否发生了变化, 最后决定是返回旧state还是新state。最后得到的state的数据结构类似存reducer的数据结构,就是键为reducer的名字,值为对应reducer的值。这个部分其实也不难。下面继续,我们看看applyMiddleware的实现
-
applyMiddleware
这个部分就是用来扩展redux的功能的。因为redux的最原始的功能就是操作state,管理state。如果我们需要在这个基础上,根据需求扩展一些功能,就需要通过使用别人编写好的中间件,或者自己编写的中间件来达到需求。比如,发起一个dispatch时,我们为了方便调试,不愿意每次自己手动console.log出这个action,这个时候编写一个logger中间件,就可以自动打印出每次dispatch发起的action,就很容易方便我们测试。又比如,我们要处理异步的action,就可以使用redux-thunk和redux-saga这类中间件。总之,该函数为我们提供了无限的可能。
我们一点点来看代码:
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) .... .... .... return { ...store, dispatch } }复制代码
先看个总览的,注意到applyMiddleware接受不定数量的中间件,然后返回一个接受一个creatStore作为参数,返回一个函数的函数。还记得我们在creatStore的时候么?那里有个场景就是
if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) }复制代码
当enhancer不为空且为函数时,就执行该函数,并return回去,作为creatStore的返回值。而这个enhancer是什么呢?翻看redux的文档:
const store = createStore(reducers, applyMiddleware(a, b, c));复制代码
哦!enhancer就是applyMiddleware的结果,就是一个 creatStore => (...args) = {}的函数。那看下enhancer的代码:
return createStore => (...args) => { const store = createStore(...args) // 1、也许有的同学一开始看到这个会有点蒙蔽, 我当时看到也是觉得奇怪, 这个dispatch的逻辑不对劲 // 而且, 还把这个dispatch作为middleware的参数传了进去,代表在中间件时使用dispatch的逻辑是这个 // 但是看到下面, dispatch = compose(...chain)(store.dispatch) // 还行, 根据作用域链, 我们可以知道在中间件中调用dispatch的时候, 其实就是调用了这个dispatch, 而不是一开始声明的逻辑 // 而这个dispatch是已经经过compose的包装的了.逻辑到这里的时候就很清楚了 let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 2、compose是如何将中间件串联在一起的? // 首先一个最简单的中间件的格式: store => next => action => {} // 这一行代码就是传入了store, 获得了 next => action => {} 的函数 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 这一行代码其实拆分成两行 // const composeRes = compose(...chain); // dispatch = composeRes(store.dispatch); // 第一行是通过compose, 将一个 这样 next => action => {} 的数组组合成 (...args) => f(g(b(...args))) 这么一个函数 // 第二行通过传入store.dispatch, 这个store.dispatch就是最后一个 next => action => {}的next参数 // 传入后 (...args) => f(g(b(...args)) 就会执行, 执行时, store.dispacth作为b的next传入, b函数结果action => {}会作为 // g的next传入, 以此类推. 所以最后dispatch作为有中间件的store的dispatch属性输出, 当用户调用dispatch时, 中间件就会一个一个 // 执行完逻辑后, 将执行权给下一个, 直到原始的store.dispacth, 最后计算出新的state dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }复制代码
跟着上面的注释,大家应该能弄懂enhancer的原理。我这里总结一下,enhancer接收一个creatStore,会在内部创建一个store,然后对该store进行增强,增强的部位在于dispatch。增强的具体方式是通过compose来构造一个dispatch链,链的具体形式就是**[中间件1,中间件2, ......, 中间件N, store.dispatch]** ,然后将增强的dispatch作为store新的dispatch暴露给用户。那用户每次dispatch的时候,就会依次执行每个中间件,执行完当前的,会将执行权交给下一个,直到reducer中,计算出新的state。
结语
网上讲解redux的源码很多,我这篇也是其中一个,主要是我个人学习源码后的,一种记录方式,加深自己印象,也为了之后忘了可以快速重温。redux其实实现上不难,但是思想上真是精髓。程序员的编码能力是一个刚需,但是设计思想是要借他山之玉,来攻石的。站在巨人的肩膀上看远方,希望自己多阅读他人的源码,在能了解原理更好运用的同时,以后自己也能创造出好用的轮子。谢谢大家花时间观看。另外,附源码地址: ,欢迎大家star和fork ,也欢迎大家和我讨论 。