目录javascript
Redux 源码能够在任意项目中的 node_modules
文件夹下的 redux
中找到。咱们阅读学习中主要关注 src 便可。java
src 下主要分红两个部分, 一部分是 utils
工具库, 一部分是 redux
逻辑代码。node
Redux 自定义的工具库react
下属对应三个文件webpack
源码以下:git
const randomString = () => Math.random() .toString(36) .substring(7) .split('') .join('.') const ActionTypes = { INIT: `@@redux/INIT${randomString()}`, REPLACE: `@@redux/REPLACE${randomString()}`, PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}` } export default ActionTypes
这个文件主要是用来对外暴露三个 action 类型,比较好理解。github
其中的 randomString
方法用来获取指定长度的随机字符串, 这里有个不少同窗都会忽略掉的知识点, Number.prototype.toString 方法 接受一个可选参数 radix
,该参数表明数字的基数, 也就是咱们常数的二进制、八进制等, 默认为 10, 范围在 2~36之间。web
源码以下:redux
export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }
这个文件对外暴露一个用来判断是否为简单对象的方法。segmentfault
简单对象
凡不是new Object()或者字面量的方式构建出来的对象都不是简单对象
就是该对象的 __proto__
等于 Object.prototype
举🌰, 像 正则, 日期,类, 函数, 都不是简单对象
具体的相关知识点,能够去看下原型链的相关知识。
源码以下:
export default function warning(message) { if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(message) } try { throw new Error(message) } catch (e) {} }
这个文件也是很简单只是用来打印一下错误信息, 这里对 console.error
加了层判断处理, 用于处理兼容性问题。缘由是 ie8及其如下都是不支持 console
下属文件以下:
咱们首先从入口文件开始阅读:
源码以下:
import createStore from './createStore' import combineReducers from './combineReducers' import bindActionCreators from './bindActionCreators' import applyMiddleware from './applyMiddleware' import compose from './compose' import warning from './utils/warning' import __DO_NOT_USE__ActionTypes from './utils/actionTypes' function isCrushed() {} if ( process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed' ) { warning( 'You are currently using minified code outside of NODE_ENV === "production". ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' + 'to ensure you have the correct code for your production build.' ) } export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes }
这里的入口文件主要的工做是把其他的几个文件作一个统一导出, 须要关注的地方有两点
__DO_NOT_USE__ActionTypes
。 经过导入路径能够得知,引用自以前 untils
中的 actionTypes.js
中,平时工做中也用不到,官方文档也未作解释,暂且放下无论,之后再研究 。。。
函数 isCrushed
从字面理解,这是函数是用来判断是不是碎的, 但函数体中却什么都没有,非常奇怪,带着这个疑问往下看这个 if
语句,在这里给出了答案,
process.env.NODE_ENV !== 'production'
typeof isCrushed.name === 'string'
isCrushed.name !== 'isCrushed'
这三个条件从字面上也很是好理解。 当三个条件都不知足时 执行 warning
抛出异常信息。信息内容翻译以下:
“您目前正在使用NODE_ENV =''production'以外的缩小代码。”+
'这意味着你正在运行一个较慢的Redux开发版本。 '+
'你可使用loose-envify(https://github.com/zertosh/loose-envify)进行浏览器化'+
'或者为Webpack定义插件(http://stackoverflow.com/questions/30030031)'+
'以确保您拥有正确的生产构建代码。
可能有同窗有对于前两个条件还好理解, 对第三个条件会感到有些疑惑, 以为没有必要。这里了解过些打包原理的同窗应该对这个判断仍是比较好理解的。
以 create-react-app
为例: 项目代码在生产环境下会对代码内容进行一个深度压缩,会将全部的变量名替换成 a
, b
, c
之类的字母, 因此当进行生成环境编译后 函数 isCrushed
能够就变成了 函数 i
。
这个函数的主要做用就是防止开发者在开发环境下对代码进行压缩, 影响调试
源码以下:
import $$observable from 'symbol-observable' import ActionTypes from './utils/actionTypes' import isPlainObject from './utils/isPlainObject' export default function createStore(reducer, preloadedState, enhancer) { // 参数处理 if ( (typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeof arguments[3] === 'function') ) { throw new Error( 'It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function' ) } 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.') } 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() { ... } function getState() { ... } function subscribe(listener) { ... } function dispatch(action) { ... } function replaceReducer(nextReducer) { ... } dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
函数 createStore
建立一个 Redux store 来以储存应用中全部的 state。应用用应有且仅有一个 store。
createStore(reducer, [preloadedState], enhancer)
返回的接口分别是 dispathc
subscribe
getState
replaceReducer
和 [$$observable]
参数
reducer: function
接收两个参数, 分别是当前的 state 树和要处理的 action, 返回新的 state 树。
[preloadedState]: any
初始时的 state, 若是只有两个参数,而且第二个参数是个函数时,将此参数赋值为第三个参数, 并忽略此参数
因此咱们在使用时能够不传第二个参数,直接将第三个参数书写到第二个参数的位置 一样能够正常使用
enhancer: func
enhaner 加强器 是一个组合 stroe creator 的高阶函数,返回一个新的强化后的 store creator。 一般能够理解为中间件,同时它也容许你经过复合函数改变 store 接口。
注意: 当存在多个参数时,且从第三个参数开始类型同为 函数, createStroe 将默认理解你想要使用多个 加强器, 这里会抛出异常提示你须要将多个加强器合并成一个函数传入。
常见的 enhancer
有 redux-thunk 以及 redux-sage。通常都会配合 applyMiddleware 一块儿使用, 其做用就是将这些 enhancer 格式化成符合 redux 要求的 enhancer。
举个🌰:
import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' import logger from 'redux-logger' import reducer from './reducers' const store = createStore(reducer, applyMiddleware(thunk)) export default store
变量
let currentState = preloadedState //从函数createStore第二个参数preloadedState得到 let currentReducer = reducer //从函数createStore第一个参数reducer得到 let currentListeners = [] //当前订阅者列表 let nextListeners = currentListeners //新的订阅者列表 let isDispatching = false
其中的 isDispatching
是做为锁来用的, 咱们都知道 redux 是一个统一的状态管理容器。因此同一时间里咱们必须保证只要一个action在执行
ensureCanMutateNextListeners
function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
确保 nextListeners 与 currentListeners 不存在同一个引用关系, 主要服务于 dispatch 与订阅。
dispatch
function dispatch(action) { if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } 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.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
这里咱们能够看到 dispatch 方法中作了三重判断,
1. action 必须是个简单函数; 2. action.type 存在; 3. 未锁住状态。
只有当三重判断都经过时, 才往下执行 action 并进行上锁
getState
function getState() { 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 }
getState 很是简单, 返回 currentState 便可。这个 currentState 在每次 dispatch 的时候都会进行更新, 同dipatch 同样在进行 reducer 操做的时候, 是不能够读取到当前 state 里的值的。
提问: 执行 createStore 函数生成 store, 可不能够直接修改它的 state?
答案是: 能够的, 但redux 不容许这么去作, 由于这样不会通知到订阅者从新更新数据。
subscribe
function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } 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) } }
这个函数能够给 store 的状态添加订阅监听函数,一旦调用 dispatch
,全部的监听函数就会执行;
nextListeners
就是储存当前监听函数的列表,调用 subscribe
,传入一个函数做为参数,那么就会给 nextListeners
列表 push
这个函数;
同时调用 subscribe
函数会返回一个 unsubscribe
函数,用来解绑当前传入的函数,同时在 subscribe
函数定义了一个 isSubscribed
标志变量来判断当前的订阅是否已经被解绑,解绑的操做就是从 nextListeners
列表中删除当前的监听函数。
replaceReducer
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) }
这个函数能够替换 store 当前的 reducer 函数,首先直接把 currentReducer = nextReducer
,直接替换;
而后 dispatch({ type: ActionTypes.INIT })
,用来初始化替换后 reducer 生成的初始化状态而且赋予 store 的状态;平时很难用到。
observable
/** * 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() { ... }
这个不是暴露给咱们开发者的, 不须要掌握。所以贴给注释,就不贴详细代码, 关于 函数 observable
有兴趣的能够去做者的 gayhub(github)去学习一下: https://github.com/tc39/proposal-observable
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))) }
这个函数的意义就是将多个函数方法合并组成一个函数并返回。 注意这里的组合是从右像左进行组合。
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) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware 须要结合到 createStore 中的第三个参数 enhancer
加强器。经过对比咱们能够得出 applyMiddleware 经过组合多个中间件返回的一个 enhaner 。
能够看一下前面 createStore 中 enhaner
的相关代码
if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) }
这里的 enhancer === applyMiddleware(...)
。。。( 读不动了 ,😳懵逼中,,, 理解不了 🤣🤣 , 跳过 跳过 😂😂)
function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) } } export default function bindActionCreators(actionCreators, dispatch) { 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"?` ) } 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') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }
这部分代码很好理解, 返回一个可以直接触发 action 的函数。
第一个参数类型应为一个函数或者对象。 当为对象时, 直接调用 bindActionCreator
方法进行返回。 当为对象时, 对对象进行遍历 将其中为 类型为 function 的 再次整合到一个对象中进行集中返回。
import ActionTypes from './utils/actionTypes' import warning from './utils/warning' import isPlainObject from './utils/isPlainObject' function getUndefinedStateErrorMessage(key, action) { ... } function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) { ... } function assertReducerShape(reducers) { ... } export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) 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}"`) } } if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let shapeAssertionError try { assertReducerShape(finalReducers) } catch (e) { shapeAssertionError = e } 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) } } 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] const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
这个js 主要用来将多个 reducer 合并成一个 进行统一返回, 工做中也比较常见。
工具方法:
assertReducerShape
function assertReducerShape(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] const initialState = reducer(undefined, { type: ActionTypes.INIT }) if (typeof initialState === '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' ) { 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.` ) } }) }
函数 assertReducerShape 将对传入的 reducer 作了两个判断处理, 两个判断分别针对于 初始的 state 未定义, 与 传入一个未知的 type 作异常处理。 经过这里咱们就不难理解 为何 reducer 中为什么必须返回一个初始的 state 与 在 switch 中的 default 返回 原有的 state。
getUnexpectedStateShapeWarningMessage
function getUnexpectedStateShapeWarningMessage( inputState, reducers, action, unexpectedKeyCache ) { 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) { 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)) { 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('", "')}"` ) } const unexpectedKeys = Object.keys(inputState).filter( key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] ) unexpectedKeys.forEach(key => { unexpectedKeyCache[key] = true }) 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.` ) } }
获取意外状态形状警告消息, 用来定义返回一下异常信息的描述内容, 供 warning 调用, 条件有: 传入的 reducer 不能为空对象、state 必须为一个简单对象、意外的 reducers 缓存。
getUndefinedStateErrorMessage
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.` ) }
获取未定义的状态错误消息
业务逻辑
assertReducerShape
检测 finalReducers 是否都有默认返回值hasChanged
来表示 state 是否产生变化,遍历reducers集合,将每一个reducer对应的原state传入其中,得出其对应的新的state。紧接着后面对新的state作了一层未定义的校验。 校验后会与原 state 进行比对,发生变化则返回 nextState, 反之返回 原 state。到这整个源码算是粗略的读了一遍,虽然代码量很少, 只有区区数百行, 可是很绕的, 到如今有些地方依旧不能理解。不过对自身的帮助仍是很大, 这期间也拜读了很多大佬的源码剖析。在这也很感谢 有那么多的大佬分享本身的学习心得体会。其中特别是 wuming 大佬的 redux源码剖析 写的十分精彩,有须要的同窗能够前往拜读一下。 这份源码阅读还存在不少理解不足, 随着后续的理解进一步深刻,也会再次更新的。