Mirrorx 源码解析

无图,纯分析,慎入

Mirror 是一个 react、redux 及 react-router 的封装。有几个显著的特点让开发变得更加简单:

  • reducers 及 effects 的函数名即为用户要使用的 actions 中的方法名。只需要定义一个模块的 reduces 和 effects (如 app 模块下定义的reducer : { add ( state ){ return state + 1 } } ),并在使用的过程中执行actions中对应模块的方法(actions.app.add(props))即可执行reducer,进而实现对 store 的修改。无需再定义 action type 及 actionCreator。
  • 在 mirror 内部对 router 进行了封装,替开发者做了将router状态集成到redux内的工作,并暴露出router的接口,无须再在项目中引用react-router。

更详细的特点参考这个指南

在实际开发中,不可避免会有大量的样板代码,选择细读 Mirror 源码,一方面是因为 Mirror 在简化 Redux
的流程上的惊艳,再也是为了学习如何对复杂的流程进行精简以方便开心的编写代码。

Mirror 的代码比较简单易懂,适合初学者掌握 redux 开发之后有一定实践后阅读。

代码

根据下面这个版本的 Mirror,写的比较枯燥同时打开对比来看会好些,另外还添加了一些注释可以作为参考:
https://github.com/zhangyu921/mirror/tree/master/src

index.js

唯一的目的暴露接口,这里用了export default {}export {}两种形式,方便引用的时候可以使用import Mirror from 'mirrorx' 或者 import {connect} from 'mirrorx' 这两种引用方法,其中Mirror是一个对象,由 export default 暴露。

defaults.js

defaults.js 暴露 options 及 defaults 方法。Mirror.defaults() 可能是使用 Mirror 第一个要调用的方法。

options,Mirror 的全局配置对象,其中有默认配置,用户调用 defaults 方法对 option 改写。

defaults 方法首先对用户传来的配置信息(historyMode, middlewares, addEffect)进行验证,随后将配置写到options里。

render.js

render 函数是比较重要的环节,在 render.js 定义,不同于 react-dom 中的render,这个函数同时兼顾了store的创建和包裹 Provider 组件的操作,所以使用 Mirror 必须要使用 render方法渲染到 DOM。

第一次执行render时,会调用createStore生成store,后面会讲到。值得注意的是当再次render函数的时候,执行了replaceReducer,这个和createStore函数在store.js中定义。

store.js

store.js 提供store,createStore和replaceReducer方法。

createStore相对于 redux 的 createStore 区别多加了一个参数models,这是在models.js中定义的所有models的数组,之后会详细说,还部署了Redux devtools,可以在devtools里对store进行直观的操作。

replaceReducer 与 store.replaceReducer 操作逻辑相同。这里除了reducers和model注册的reducers,还有一个routing,即为react-router-redux的routerReducers。所以在routing不能再次被定义。

createStore与replaceReducer同时引用了一个方法 createReducer。这是因为Mirror里reducer定义在models里,在生成store或者调用store.replaceReducer时需要重新组合reducers。

model.js

model.js 提供了 Mirrorx 中最常用的方法 model,也暴露出models记录用户定义的model的集合,但它本身做的事情并不多。

model方法首先对用户传来的参数m进行了校验。validateModel中的函数filterReduces方法,确保了reducers里仅有function类型的属性。

第9行开始,总的来看是用 getReducer 方法获得了reducer,然后存到_model再直接存到了models里。随后执行了addActions()。

我们细看getReducer做了什么事情,返回一个方法,这个方法接收state和action,返回reducers[action.type](state, action.data)也就是执行这个reducers里的action.type变量代表的方法。

到这里我们知道resolveReducers做的事情应该是返回了一个对象,每个键名代表action.type,值是接收state和action.data的函数。

actions.js

actions.js对外暴露了上面model.js用到的 resolveReducers、addActions方法,还有一个actions对象供用户调用。

resolveReducers 功能如上所述,根据modelName、SEP和当前键名组合新的键名,值为reducer方法。

addActions 方法用了两次遍历 分别将reducers和effects做了处理,对reducers的处理是将actionCreater返回的结果根据modelName和actionNave直接写到actions里。actionCreator方法接收两个name,返回一个方法,一眼看过去就明白这其实就是封装了 dispatch 方法,这个方法是在middleware中引入的,这个后面会说。

addActions在遍历effects的时候与reducers基本相同,区别是调用了options.addEffect和添加了一个标志位。猜测在调用effect的时候,会根据标志位进行不同的操作。需要注意的是options.addEffect与effects.js中的addEffects不同,option中的是执行过effects.js之后的结果。

可以看出actions.js中做的工作主要是将dispatch函数写到到actions对象里。

middleware.js

middleware.js 提供了 actions.js 中的dispatch函数,以及getState和createMiddleware方法。

在调用createMiddleware之前,dispatch和getState方法都相当于是warning方法,createMiddleware方法是在render的时候调用的createStore方法中 applyMiddleware 的时候执行的,换句话说必须使用Mirror自身的render方法。

createMiddleware顾名思义就是个中间件,中间件写法大家应该熟悉

store => next => action => next(action)

所以这里的MiddlewareAPI即为store,取出dispatch和getState赋值到外层,Mirror.actions所使用的dispatch才能正常使用。将这个操作放在中间件中,会使得每一次调用中间件都先对dispatch重新赋值。

createMiddleware也负责了对effects的接收调用。 也就是说在Mirror中,对Effect的调用相当于在中间件中看是否有Effect注册了,如果有就调用,然后把之前的结果舍弃。

随后依次调用hooks,这里也说明hooks是在中间件做处理。

effects.js

effects.js 提供了 effects 的集合,和一个方法addEffect方法。整体很简单,不在赘述。

hook.js

hook.js 提供一个hooks的集合和一个hook方法直接在 index.js 中被暴露出来。很显然hook方法用于注册钩子函数到hooks数组。返回一个函数,这个函数被执行之后,会将指定的钩子函数剔除。

router.js & routerMiddleware.js

这两个模块主要作用将 react-router集成到Mirror中。应该是集成react-router必要的步骤,在这里不作重点了。

值得注意 routerMiddleware 是第一个中间件,当接受到指定的action.type后操作跳转,短路后续操作。

总结

这时候对Mirror的工作流程就非常清晰了:

初始化:

Mirror.defaults定义配置
-> 各个模块定义的Mirror.model 组织reducers、actions
-> Mirror.render 生成store、包裹Provider、渲染DOM

调用actions:

执行某个action
-> 相当于执行dispatch
-> 对应Store中reducer执行,相当于用户定义的reducer执行

调用effects:

同样执行某个action
-> 但在reducers中没有这个action type( render.js Line:30 参数model不包括effects )
-> 中间件拦截之后,在effects中找到这个函数并执行

好的,结束~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容