注:redux核心代码位于src目录下,因此以目录结构来分析,文中仅粘贴部分核心代码。
src
index.js
import createStore from './createStore'// 省略导入......// 定义一个空函数function isCrushed() {}// 代码压缩后,会对变量进行重命名,也就是通过这种方式,减少字节,判断函数名是否发生变化// 来确定是否使用了压缩后的版本。if ( process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed') { // 判断函数的运行环境,在非生产环境下运行时,isCrushed函数名不是'isCrushed',则说明 // 自视运行的是压缩后代码,对开发者提出警告⚠️开发环境下使用压缩文件,会增加额外的构建时间。 warning( 'You are currently using minified code outside of NODE_ENV === "production"......' )}// 输出redux的核心apiexport { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes}复制代码
createStore
import $$observable from 'symbol-observable'import ActionTypes from './utils/actionTypes'import isPlainObject from './utils/isPlainObject'/** * @param {Function} reducer A function that returns the next state tree, given * the current state tree and the action to handle. * * @param {any} [preloadedState] The initial state. You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session. * If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys. * * @param {Function} [enhancer] The store enhancer. You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux * is `applyMiddleware()`. * * @returns {Store} A Redux store that lets you read the state, dispatch actions * and subscribe to changes. */// createStore构建一个全局唯一的store用于存储全局状态。store是一个树?结构对象,可读并在一定情况下// 可写,当然你也可以直接对sotre对象进行改写,那么产生的出乎意料的bug也会让你惊喜,redux本身并没有对// store的写操作进行限制,全凭使用者的自觉。ps:为了不出bug,不要直接改写store数据。export default function createStore(reducer, preloadedState, enhancer) { // createStore接收最多三个参数 // @reducer<:Function> 一个纯函数衍生器,用于从旧的状态,衍生新的状态,并且这个新状态是全新的拷贝。 // preloadedState<:Object> 初始化状态,用于从一个基础状态构建一个初始store。 // enhance<:Function> reducer的增强器,可以调用比如中间件函数等,来增强redux功能。 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { // enhancer可能是第二个参数,此时需要纠正各参数值。曾考虑为什么不将enhancer和preloadedState参数 // 调换位置,如此便不需要这一步处理,后想到大多数情况下我们可能不会使用enhancer,所以在参数设计时,尽量 // 将次要参数的位置向后,或通过对象{key:value}的形式传入。 enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { // enhancer必须为一个function throw new Error('Expected the enhancer to be a function.') } // 如果传入了enhancer,则使用enhaner增强createReducer函数,最后生成store return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function') { // 类型校验 throw new Error('Expected the reducer to be a function.') } // 预定义一系列可用变量 let currentReducer = reducer let currentState = preloadedState let currentListeners = [] let nextListeners = currentListeners let isDispatching = false function ensureCanMutateNextListeners() { // 注册事件存储在栈中,当前后两次栈相同时,进行一次拷贝,因为通常操作的nextListeners,这是 // 最新的事件栈。防止在操作nextListeners时进行了写操作,导致修改了currentListeners。 if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } /** * @returns {any} The current state tree of your application. */ function getState() { // getState方法很简单,直接返回当前的store,即返回currentState。获取store时,可能 // 正处于某个action在dispatch的状态,那么此时的state,我们必然可以从组件内获取,不需要 // 通过api来获取。 // 假设:如果在dispatch时获取state,这种行为往往和我们预期不太相同,因为我们通常想要获取的 // 是最新状态的state,而此时的store状态即将被更新。 if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing...... ' ) } return currentState } /** * @param {Function} listener A callback to be invoked on every dispatch. * @returns {Function} A function to remove this change listener. */ function subscribe(listener) { // redux支持订阅store状态的变化,可向subscribe传入一个注册函数。 if (typeof listener !== 'function') { // 类型校验 throw new Error('Expected the listener to be a function.') } if (isDispatching) { // 禁止在dispatch过程中进行订阅。 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 // 确保可以安全操作nextListeners ensureCanMutateNextListeners() // 将订阅函数推入订阅栈 nextListeners.push(listener) // 订阅完成后,返回一个取消订阅的方法 return function unsubscribe() { if (!isSubscribed) { // 防止反复取消订阅,后面使用了splice方法,保证不会取订错误的函数 return } if (isDispatching) { // dispatch时禁止取消订阅 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) } } /** * Dispatches an action. It is the only way to trigger a state change. * * The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will * be considered the **next** state of the tree, and the change listeners * will be notified. * * The base implementation only supports plain object actions. If you want to * dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware. For * example, see the documentation for the `redux-thunk` package. Even the * middleware will eventually dispatch plain object actions using this method. * * @param {Object} action A plain object representing “what changed”. It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have * a `type` property which may not be `undefined`. It is a good idea to use * string constants for action types. * * @returns {Object} For convenience, the same action object you dispatched. * * Note that, if you use a custom middleware, it may wrap `dispatch()` to * return something else (for example, a Promise you can await). */ // dispatch是唯一可以改变store的渠道,action要求只能是简单对象,并包含一个type属性 function dispatch(action) { if (!isPlainObject(action)) { // action需要是一个简单函数,可以是对象字面量 a = {} 或 Object.create(null) throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { // 要求action必须包含type属性 throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { // 不可以同时进行多个dispatch,避免同时修改store树?,导致状态紊乱 throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true // 进入dispatching状态,此时会禁止一些特殊操作,如上面的subscribe, unsubscribe, getState // 传入reducer当前currentState和action生成新的store currentState = currentReducer(currentState, action) } finally { // store更新完毕后,修改dispatching状态 isDispatching = false } // dispatch后执行订阅的回调函数,到了兑现约定的时候了,需要通知订阅函数,store发生变化了。 // 并把listerners进行更新 // 思考:其实可以在listerner复制这里进行拷贝,便不需要ensureCanMutateNextListeners函数了 const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] // 问题:为什么不把新的store传给回调函数???通过getstate吗? // 答案:想了一下,传递store给监听函数,有可能我们并不需要使用store,那么传递后还需要一个额外 // 的指针内存。可以在需要时,通过getState获取最新的store。 listener() } // 返回action return action } /** * @param {Function} nextReducer The reducer for the store to use instead. * @returns {void} */ function replaceReducer(nextReducer) { // 作用如名字,替换reducer。如store一版,reducer也只需要一个。但在代码中,为了优化,可能 // 会做一些代码分割的操作。可能会根据模块分割store,分割reducer。但最终还是需要将reducer // 组装在一起的。 if (typeof nextReducer !== 'function') { // 参数类型检验,nextReducer必须是函数 throw new Error('Expected the nextReducer to be a function.') } // 替换reducer,触发一个全局action, 通知reducer发生了替换。 // 问题:为什么此处不做isDispatching的状态限制? currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) } /** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */ function observable() { const outerSubscribe = subscribe return { /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ subscribe(observer) { if (typeof observer !== 'object' || observer === null) { 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 } } } // 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 }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }}复制代码
compose
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @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) { // compose函数,可将多个函数参数组成合并成一个函数,参数从右自左执行。 if (funcs.length === 0) { return arg => arg } // 思考: 需要加一步对参数类型的校验,否则会引发错误。 // if (funcs.some((func) => !func instanceof Function)) { // throw new Error('所有参数需要是函数类型') // } if (funcs.length === 1) { return funcs[0] } // reduce遍历整个参数序列,时间复杂度On(n)。合并成的对象参数将取自最后一个函数的参数。 return funcs.reduce((a, b) => (...args) => a(b(...args)))}复制代码
combineReducer
import ActionTypes from './utils/actionTypes'import warning from './utils/warning'import isPlainObject from './utils/isPlainObject'function getUndefinedStateErrorMessage(key, action) { const actionType = action && action.type const actionDescription = (actionType && `action "${ String(actionType)}"`) || 'an action' return ( `Given ${actionDescription}, reducer "${key}" returned undefined. ` + `To ignore an action, you must explicitly return the previous state. ` + `If you want this reducer to hold no value, you can return null instead of undefined.` )}// 此方法用于检查store和reduces的key是否完全匹配,不匹配则发出提示function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache) { // 获取传入reducers的序列key。 const reducerKeys = Object.keys(reducers) const argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer' if (reducerKeys.length === 0) { // combineReducers接收的reducers参数必须是一个对象,内容为key:value序列。 return ( 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.' ) } if (!isPlainObject(inputState)) { // inputState需为简单对象 return ( `The ${argumentName} has unexpected type of "` + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + `". Expected argument to be an object with the following ` + `keys: "${reducerKeys.join('", "')}"` ) } // state和reducers必须key匹配,unexpectedKeys为inputState中不与reducers匹配的key const unexpectedKeys = Object.keys(inputState).filter( key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] ) // 将不匹配的key传送给unexpectedKeyCache参数 unexpectedKeys.forEach(key => { unexpectedKeyCache[key] = true }) // 如果在发生replaceReducer,则退出这段程序 if (action && action.type === ActionTypes.REPLACE) return if (unexpectedKeys.length > 0) { return ( `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + `Expected to find one of the known reducer keys instead: ` + `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.` ) }}// 此方法用于检验reducer是否做了默认处理,在任何情况下都需要衍生一个新的state或返回currentState或者nullfunction assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT }) // 测试reducer功能边界条件处理是否健全 if (typeof initialState === 'undefined') { // 保证传入的reducers中总有一个处理通用type的case,即我们常写的reducer函数switch语句中 // 对于default情况的处理,通常返回上一个状态的store。可以返回null但是不能返回undefined。 throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined. If you don't want to set a value for this reducer, ` + `you can use null instead of undefined.` ) } if ( typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION() }) === 'undefined' ) { // 通过一个随机的验错type❌,来检查reducer是否对默认情况进行了处理。对于任何未知的type, // reducer都需要有一个合格的返回值。并且警告⚠️不能够对redux中内置的几个type做处理。 throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don't try to handle ${ ActionTypes.INIT } or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined, but can be null.` ) } })}/** * @param {Object} reducers An object whose values correspond to different * reducer functions that need to be combined into one. One handy way to obtain * it is to use ES6 `import * as reducers` syntax. The reducers may never return * undefined for any action. Instead, they should return their initial state * if the state passed to them was undefined, and the current state for any * unrecognized action. * * @returns {Function} A reducer function that invokes every reducer inside the * passed object, and builds a state object with the same shape. */// 合并reducers的核心方法,返回一个全新的reducer。export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} // 遍历reducers for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { // 在非生产环境下,reducers的每一个key都需要有一个子reducer作为值,不能为undefined warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === 'function') { // 仅在reducers的各个属性值为函数时进行复制 finalReducers[key] = reducers[key] } } // finalReducers的获取也可以这么写 // const finalReducers = reducers.reduce((res, reducer, key) => { // if (reducer instanceof Function) { // res[key] = reducer; // } else { // if (process.env.NODE_ENV !== 'production') { // warning(`No reducer provided for key "${key}"`) // } // } // return res; // }, {}) const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let shapeAssertionError try { // 检验reducer是否合规 assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } // 最终返回的合并后的reducer return function combination(state = {}, action) { if (shapeAssertionError) { // 如果reducer不合规,抛出错误。? throw shapeAssertionError } if (process.env.NODE_ENV !== 'production') { // 非生产环境下,检查state和reduces的key是否完全匹配 const warningMessage = getUnexpectedStateShapeWarningMessage( state, finalReducers, action, unexpectedKeyCache ) if (warningMessage) { // 如果检验不合规,进行提示 warning(warningMessage) } } // 前后状态是否发生变化 let hasChanged = false const nextState = {} for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] // 使用reducer来衍生新状态 const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { // 如果某个action中没对所有的边界条件进行处理,抛出错误。 const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey // 判断前后状态是否发生了变化,只要有部分状态发生变化,则store发生了变化 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // store发生变化,返回新的状态,否则返回旧状态 return hasChanged ? nextState : state }}复制代码
bindActionCreators
function bindActionCreator(actionCreator, dispatch) { // 核心方法,为action生成器绑定dispatch,不需要每次从store.dispatch获取,当然这种方式也可以。 return function() { // 为actioncreater绑定运行时时上下文环境,确保其执行时this指向其所属环境 return dispatch(actionCreator.apply(this, arguments)) }}/** * @param {Function|Object} actionCreators An object whose values are action * creator functions. One handy way to obtain it is to use ES6 `import * as` * syntax. You may also pass a single function. * * @param {Function} dispatch The `dispatch` function available on your Redux * store. * * @returns {Function|Object} The object mimicking the original object, but with * every action creator wrapped into the `dispatch` call. If you passed a * function as `actionCreators`, the return value will also be a single * function. */// 输出方法export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { // 如果直接传入了一个函数,则直接进行绑定 return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null) { // actionCreators类型校验,必须为对象类型或函数。 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"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { // 检验actionCreators中每个actionCreator是否为函数 // 仅为actionCreator是函数的部分绑定dispatch boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } // 返回绑定dispatch后的actionCreators return boundActionCreators}复制代码
applyMiddleware
import compose from './compose'/** * Creates a store enhancer that applies middleware to the dispatch method * of the Redux store. This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner, or logging every action payload. * * See `redux-thunk` package as an example of the Redux middleware. * * Because middleware is potentially asynchronous, this should be the first * store enhancer in the composition chain. * * Note that each middleware will be given the `dispatch` and `getState` functions * as named arguments. * * @param {...Function} middlewares The middleware chain to be applied. * @returns {Function} A store enhancer applying the middleware. */export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) 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) } // 遍历调用middware增强dispatch方法,中间件可以链式合并。 const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}复制代码
utils
actionTypes
/** * These are private action types reserved by Redux. * For any unknown actions, you must return the current state. * If the current state is undefined, you must return the initial state. * Do not reference these action types directly in your code. */const randomString = () => Math.random() .toString(36) .substring(7) .split('') .join('.')// redux内置的action type, 用于在store初始化,replaceReducer,注入代码时通知或校验const ActionTypes = { INIT: `@@redux/INIT${randomString()}`, REPLACE: `@@redux/REPLACE${randomString()}`, PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`}export default ActionTypes复制代码
isPlainObject
/** * @param {any} obj The object to inspect. * @returns {boolean} True if the argument appears to be a plain object. */export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { // 最终proto为Object proto = Object.getPrototypeOf(proto) } // 判断obj是不是对象字面量或Object构建的实例对象 return Object.getPrototypeOf(obj) === proto}复制代码
warning
/** * Prints a warning in the console if it exists. * * @param {String} message The warning message. * @returns {void} */export default function warning(message) { /* eslint-disable no-console */ // 判断console方法是否可调用 if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(message) } /* eslint-enable no-console */ // 抛出警告信息⚠️,如果开启异常断点,则会在自处停止 try { // This error was thrown as a convenience so that if you enable // "break on all exceptions" in your console, // it would pause the execution at this line. throw new Error(message) } catch (e) {} // eslint-disable-line no-empty}复制代码
Redux阅读体验
学习到
-
Object.keys(someobj).map((v, k) => someobj[k])
可以替代for...of...
支持es6可以使用Objec.entries -
compose方法的reduce实现,reduce真的很强!
(...args) => (..params) => args.reduce((res, curr) => res(curr(..params)))// reduce便捷在于返回的无限制和累积效应// 无论是map, foreach, for...in...都无法达到这么优秀的品质复制代码
-
let lock = true;/* somewhere */ lock = false;
想不到合适名字,就叫运行锁,开发中这种模式很常用,一个bool类型的变量,便可以区别两种状态,就像寄存器的两个状态。
-
接口设计
export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes}复制代码
只暴露尽可能少且必要的接口,接口名可以体现功效。 复制代码
- 代码死,人要灵活,观察到语言的规则和现象,都可以被利用。
function isCrushed() {} 在压缩情况下isCrushed.name !== 'isCrushed'复制代码
-
边界校验,是保证代码质量的必要手段。
js是动态语言,所以变量的类型运行时可被任意改变,类型校验就更必要了。
优点
- redux自身体积很小,小几百行代码,对于一个大型项目来说,体积上简直是冰山一角,所以不必担心包大小。
- flux架构的践行者,结合 react,mvc工作流,管理你的应用状态。
- 单向流,以前总觉的单向流用起来很不爽快,状态链任性组织,随着工程越来越大,维护也越来越难,难到自己写的代码都看不清晰。所以要让你的应用易于组织,易于维护,那就让你的数据流尽量简单。就像,世间凡事大河,定然不会有勾缠旋绕,方能源远流长,万流汇聚。
- 源码读起来很流畅,了解flux架构后,完全可以自己阅读源码进行学习。
不足
- 细粒度控制不够好,对于状态的更新,监听(subscribe)都是全量的,可以比较mobx。
- 不支持异步操作,需要配合thunk,saga等中间件。
- 单向流意味着灵活度的降低,当然这是易于组织维护的代价,并且需要额外增加许多模版代码。
- reducers的合并也仅仅支持到一维,超复杂场景或许需要一个树?结构的reducer。