redux源码解析实例教程
程序员文章站
2022-03-26 17:49:52
redux 解析
目录结构
├── applymiddleware.js // 使用中间件模块,有了它就可以组合中间件一起使用
├── bindactioncreators.j...
redux 解析
目录结构
├── applymiddleware.js // 使用中间件模块,有了它就可以组合中间件一起使用 ├── bindactioncreators.js ├── combinereducers.js ├── compose.js // 组成函数,用于把函数列表组合成一个嵌套执行的函数 ├── createstore.js ├── index.js // 主函数,用于导出库的 api 函数 └── utils // 工具文件夹,用于存放一些工具函数 ├── actiontypes.js ├── isplainobject.js └── warning.js
index.js
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' /* * 这是一个空函数,用于检查当前代码是否被压缩,如果当前代码是压缩后的, * 且当前环境不为 production 那么那么警告用户 */ 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 }
工具函数
我们可以看出, redux 引用了三个工具函数文件,且这三个工具函数是不对外暴露的,那么这三个工具函数文件中分别有什么内容呢?现在我们来简单的解析一下
actiontype.js
/* * 一个私有的函数,用于生成一个随机字符串 * 使用 tostring(36) 就是利用 tostring 的特性将其转化成 36 进制的, 为 0-9, a-z * 然后从 substring(7) 从第 7 位开始取内容,这样就可以取一个有效的,不含 0. 且位数足够长也足够短的字符串 */ const randomstring = () => math.random() .tostring(36) .substring(7) .split('') .join('.') /* * 生成一个 actiontypes 类型,就是一个单纯的对象 */ const actiontypes = { init: `@@redux/init${randomstring()}`, replace: `@@redux/replace${randomstring()}`, probe_unknown_action: () => `@@redux/probe_unknown_action${randomstring()}` } export default actiontypes
isplainobject.js
/* * 用于检查传入的参数是不是一个简单的对象 * 主要是用于检查这个对象是不是被继承 */ 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 }
warning.js
/* * 导出一个函数用于输出警告信息 */ export default function warning(message) { // 如果 console 对象存在,且有 error 方法,那么就用它输出错误信息 // 之所以这么写,是因为某些情况化,会重新定义 console 以防止别人调试代码 if (typeof console !== 'undefined' && typeof console.error === 'function') { console.error(message) } try { // 抛出异常 throw new error(message) } catch (e) {} }
compose.js
/* * 组成函数,用于把函数列表组合成一个嵌套执行的函数 */ export default function compose(...funcs) { // 如果没有传入参数,那么直接返回一个无用函数 // function (arg) { return arg } if (funcs.length === 0) { return arg => arg } // 如果只传入了一个参数,那么就返回第一个函数 if (funcs.length === 1) { return funcs[0] } // 如果传入的函数列表有很多项目,那么就组全成一个嵌套执行的函数,并返回 // 比如传入的的是 compose(a, b, c, d, e) // 那么返回 a(b(c(d(e(...args))))), 在函数外只有 let func = compose(a,b,c,d,e) func(12,13) 就可以调用执行了 return funcs.reduce((a, b) => (...args) => a(b(...args))) }
applymiddleware.js
import compose from './compose' /* * 导出一个函数,用于使用中间件模块,有了它就可以组合中间件一起使用 */ export default function applymiddleware(...middlewares) { // 创建一个 store 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.` ) } // 给中间件提供了 store.getstate 方法与 dispatch 方法,相当于提供了一个简易的 store const middlewareapi = { getstate: store.getstate, dispatch: (...args) => dispatch(...args) } // 挨个遍历执行中间件 const chain = middlewares.map(middleware => middleware(middlewareapi)) // 把多个中间件组合起来, 如果存在 fn1, fn2, fn3, fn4 那么 dispatch 就会变成 // dispatch = fn1(fn2(fn3(fn4(store.dispatch)))) dispatch = compose(...chain)(store.dispatch) // 返回中间件处理过的 store, dispatch return { ...store, dispatch } } }
bindactioncreators.js
/* * 返回一个 dispatch 函数,这个函数是绑定了 this 对象的。 this 指向其初始传入的对象 */ function bindactioncreator(actioncreator, dispatch) { return function() { return dispatch(actioncreator.apply(this, arguments)) } } /* * 导出一个工具函数,用于将 dispatch 绑定 this 上下文 */ export default function bindactioncreators(actioncreators, dispatch) { // 如果传入的是一个函数,一般情况就是用 function 模拟的 class, 这个时候直接返回绑定了上下文的函数 if (typeof actioncreators === 'function') { return bindactioncreator(actioncreators, dispatch) } // 如果传入的不是一个对象,那么抛出异常 // 私以为判断函数可以这么写 object.prototype.tostring.call(actioncreators) !== '[object object]' 比较好 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"` ) } // 如果传入的是一个对象,那么遍历对象的属性,对对象中每一个是 function 的属性进行绑定,然后返回 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 }
combinereducers.js
import actiontypes from './utils/actiontypes' import warning from './utils/warning' import isplainobject from './utils/isplainobject' // 如果要从 state 中出数据,但是 state 中是不存在该属性的,那么组合一个对应的错误信息 // 告诉用户在哪一个 action 中,取什么类型的数据错误 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.` ) } // 获取意外状态下的错误形成信息 function getunexpectedstateshapewarningmessage( inputstate, reducers, action, unexpectedkeycache ) { const reducerkeys = object.keys(reducers) // 检查是 store 初始的时候形成的还是收到 action 时形成 const argumentname = action && action.type === actiontypes.init 'preloadedstate argument passed to createstore' : 'previous state received by the reducer' // 如果没有传入 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.' ) } // 如果传入的 state 不是一个简单对象,那么返回错误信息,告知用户 state 应为简单对象 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('", "')}"` ) } // 先收集未预料的 key。这些 key 不存在于 reducers 中也不存在于 unexpectedkeycache 中 const unexpectedkeys = object.keys(inputstate).filter( key => !reducers.hasownproperty(key) && !unexpectedkeycache[key] ) // 把所有的未预料 key 放入缓存中 unexpectedkeys.foreach(key => { unexpectedkeycache[key] = true }) // 如果当前 action 类型是 replace 那么洗洗睡吧,啥都不干了 if (action && action.type === actiontypes.replace) return // 如果存在未预料的 key 那么组装错误信息并返回 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 模型 function assertreducershape(reducers) { // 对传入的 reducers 列表中的每一个 reducer 来进行断言操作 object.keys(reducers).foreach(key => { const reducer = reducers[key] const initialstate = reducer(undefined, { type: actiontypes.init }) // 如果初始 state 为 undefined ,那么抛出异常,因为初始 state 要为对象类型 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.` ) } // 这里用于检测 action 的 type 处理是否有 default 如果没有,那么也会抛出一个异常,因为 reducer 至少返回一个稳定的 state 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.` ) } }) } /** * 导出一个公共函数,用于把传入的多个 reducer 对象组合成一个对象,以确保在 reducer 分门别类的同时 store 唯一 */ export default function combinereducers(reducers) { const reducerkeys = object.keys(reducers) const finalreducers = {} for (let i = 0; i < reducerkeys.length; i++) { const key = reducerkeys[i] // 如果当前是开发环境,且引用了没有定义好的 action ,那么报出警告 if (process.env.node_env !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`no reducer provided for key "${key}"`) } } // 如果 reducers[key] 是一个 function 类型的,那么说明它是最终的 reducer ,将它放入结果对象中 if (typeof reducers[key] === 'function') { finalreducers[key] = reducers[key] } } const finalreducerkeys = object.keys(finalreducers) let unexpectedkeycache if (process.env.node_env !== 'production') { unexpectedkeycache = {} } // 断言最终的结果对象是一个 reducer let shapeassertionerror try { assertreducershape(finalreducers) } catch (e) { shapeassertionerror = e } // 返回结合函数,这个函数其实也就是最终的 reducer return function combination(state = {}, action) { // 如果结果对象不是一个 reducer 那么抛出异常 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 = {} // 每次调用 reducers 的时候遍历执行,如果有变化,就返回新值,否则就返回原值 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 } }
createstore.js
import $$observable from 'symbol-observable' import actiontypes from './utils/actiontypes' import isplainobject from './utils/isplainobject' /* * 导出一个共工的 createstore 方法,用于创建唯一的 store */ export default function createstore(reducer, 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.') } return enhancer(createstore)(reducer, preloadedstate) } // 检查 reducer 的类型 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() { if (nextlisteners === currentlisteners) { nextlisteners = currentlisteners.slice() } } /* * 获取 state 内容,如果当前正在操作,那么抛出异常 */ 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 } /* * 注册观察者 */ 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) } } /* * 分发 action */ function dispatch(action) { // 检查 action 类型是否为简单对象 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.') } // 执行 reducer 里面的 action 具体内容 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 } /* * 更换 reducer */ function replacereducer(nextreducer) { if (typeof nextreducer !== 'function') { throw new error('expected the nextreducer to be a function.') } currentreducer = nextreducer dispatch({ type: actiontypes.replace }) } /* * 可监听的,其实就是观察者模式的一种实现方式 */ function observable() { const outersubscribe = subscribe return { 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 } } } // 初始化 dispatch({ type: actiontypes.init }) return { dispatch, subscribe, getstate, replacereducer, [$$observable]: observable } }
上一篇: 聊天室开发详解(三)
下一篇: 【数据结构-C++语言】图篇
推荐阅读
-
vue数据控制视图源码解析
-
PHP中实现汉字转区位码应用源码实例解析
-
Vue源码解析之Template转化为AST的实现方法
-
jQuery 源码解析(二十二) DOM操作模块 复制元素 详解
-
解析xHTML源码的DLL组件AngleSharp介绍
-
Mybaits 源码解析 (十一)----- 设计模式精妙使用:静态代理和动态代理结合使用:@MapperScan将Mapper接口生成代理注入到Spring
-
Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
-
Ribbon源码解析
-
Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
-
Eureka获取服务列表源码解析