游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

手写一个Redux,深入理解其原理

  Redux可是一个大名鼎鼎的库,很多地方都在用,我也用了几年了,今天这篇文章就是自己来实现一个Redux,以便于深入理解他的原理。我们还是老套路,从基本的用法入手,然后自己实现一个Redux来替代源码的NPM包,但是功能保持不变。本文只会实现Redux的核心库,跟其他库的配合使用,比如ReactRedux准备后面单独写一篇文章来讲。有时候我们过于关注使用,只记住了各种使用方式,反而忽略了他们的核心原理,但是如果我们想真正的提高技术,最好还是一个一个搞清楚,比如Redux和ReactRedux看起来很像,但是他们的核心理念和关注点是不同的,Redux其实只是一个单纯状态管理库,没有任何界面相关的东西,ReactRedux关注的是怎么将Redux跟React结合起来,用到了一些React的API。基本概念
  Redux的概念有很多文章都讲过,想必大家都看过很多了,我这里不再展开,只是简单提一下。Redux基本概念主要有以下几个:Store
  人如其名,Store就是一个仓库,它存储了所有的状态(State),还提供了一些操作他的API,我们后续的操作其实都是在操作这个仓库。假如我们的仓库是用来放牛奶的,初始情况下,我们的仓库里面一箱牛奶都没有,那Store的状态(State)就是:{milk:0}Actions
  一个Action就是一个动作,这个动作的目的是更改Store中的某个状态,Store还是上面的那个仓库,现在我想往仓库放一箱牛奶,那我想往仓库放一箱牛奶就是一个Action,代码就是这样:{type:PUTMILK,count:1}Reducers
  前面我想往仓库放一箱牛奶只是想了,还没操作,具体操作要靠Reducer,Reducer就是根据接收的Action来改变Store中的状态,比如我接收了一个PUTMILK,同时数量count是1,那放进去的结果就是milk增加了1,从0变成了1,代码就是这样:constinitState{milk:0}functionreducer(stateinitState,action){switch(action。type){casePUTMILK:return{。。。state,milk:state。milkaction。count}default:returnstate}}
  可以看到Redux本身就是一个单纯的状态机,Store存放了所有的状态,Action是一个改变状态的通知,Reducer接收到通知就更改Store中对应的状态。简单例子
  下面我们来看一个简单的例子,包含了前面提到的Store,Action和Reducer这几个概念:import{createStore}fromredux;constinitState{milk:0};functionreducer(stateinitState,action){switch(action。type){casePUTMILK:return{。。。state,milk:state。milkaction。count};caseTAKEMILK:return{。。。state,milk:state。milkaction。count};default:returnstate;}}letstorecreateStore(reducer);subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用如果是结合页面更新,更新的操作就是在这里执行store。subscribe(()console。log(store。getState()));将action发出去要用dispatchstore。dispatch({type:PUTMILK});milk:1store。dispatch({type:PUTMILK});milk:2store。dispatch({type:TAKEMILK});milk:1自己实现
  前面我们那个例子虽然短小,但是已经包含了Redux的核心功能了,所以我们手写的第一个目标就是替换这个例子中的Redux。要替换这个Redux,我们得先知道他里面都有什么东西,仔细一看,我们好像只用到了他的一个API:
  createStore:这个API接受reducer方法作为参数,返回一个store,主要功能都在这个store上。
  看看store上我们都用到了啥:store。subscribe:订阅state的变化,当state变化的时候执行回调,可以有多个subscribe,里面的回调会依次执行。
  store。dispatch:发出action的方法,每次dispatchaction都会执行reducer生成新的state,然后执行subscribe注册的回调。
  store。getState:一个简单的方法,返回当前的state。
  看到subscribe注册回调,dispatch触发回调,想到了什么,这不就是发布订阅模式吗?我之前有一篇文章详细讲过发布订阅模式了,这里直接仿写一个。functioncreateStore(){letstate;state记录所有状态letlisteners〔〕;保存所有注册的回调functionsubscribe(callback){listeners。push(callback);subscribe就是将回调保存下来}dispatch就是将所有的回调拿出来依次执行就行functiondispatch(){for(leti0;ilisteners。length;i){constlistenerlisteners〔i〕;listener();}}getState直接返回statefunctiongetState(){returnstate;}store包装一下前面的方法直接返回conststore{subscribe,dispatch,getState}returnstore;}
  上述代码是不是很简单嘛,Redux核心也是一个发布订阅模式,就是这么简单!等等,好像漏了啥,reducer呢?reducer的作用是在发布事件的时候改变state,所以我们的dispatch在执行回调前应该先执行reducer,用reducer的返回值重新给state赋值,dispatch改写如下:functiondispatch(action){statereducer(state,action);for(leti0;ilisteners。length;i){constlistenerlisteners〔i〕;listener();}}
  到这里,前面例子用到的所有API我们都自己实现了,我们用自己的Redux来替换下官方的Redux试试:import{createStore}fromredux;import{createStore}from。myRedux;
  可以看到输出结果是一样的,说明我们自己写的Redux没有问题:
  了解了Redux的核心原理,我们再去看他的源码应该就没有问题了,createStore的源码传送门。
  最后我们再来梳理下Redux的核心流程,注意单纯的Redux只是个状态机,是没有View层的哦。
  除了这个核心逻辑外,Redux里面还有些API也很有意思,我们也来手写下。手写combineReducers
  combineReducers也是使用非常广泛的API,当我们应用越来越复杂,如果将所有逻辑都写在一个reducer里面,最终这个文件可能会有成千上万行,所以Redux提供了combineReducers,可以让我们为不同的模块写自己的reducer,最终将他们组合起来。比如我们最开始那个牛奶仓库,由于我们的业务发展很好,我们又增加了一个放大米的仓库,我们可以为这两个仓库创建自己的reducer,然后将他们组合起来,使用方法如下:import{createStore,combineReducers}fromredux;constinitMilkState{milk:0};functionmilkReducer(stateinitMilkState,action){switch(action。type){casePUTMILK:return{。。。state,milk:state。milkaction。count};caseTAKEMILK:return{。。。state,milk:state。milkaction。count};default:returnstate;}}constinitRiceState{rice:0};functionriceReducer(stateinitRiceState,action){switch(action。type){casePUTRICE:return{。。。state,rice:state。riceaction。count};caseTAKERICE:return{。。。state,rice:state。riceaction。count};default:returnstate;}}使用combineReducers组合两个reducerconstreducercombineReducers({milkState:milkReducer,riceState:riceReducer});letstorecreateStore(reducer);store。subscribe(()console。log(store。getState()));操作的actionstore。dispatch({type:PUTMILK,count:1});milk:1store。dispatch({type:PUTMILK,count:1});milk:2store。dispatch({type:TAKEMILK,count:1});milk:1操作大米的actionstore。dispatch({type:PUTRICE,count:1});rice:1store。dispatch({type:PUTRICE,count:1});rice:2store。dispatch({type:TAKERICE,count:1});rice:1
  上面代码我们将大的state分成了两个小的milkState和riceState,最终运行结果如下:
  知道了用法,我们尝试自己来写下呢!要手写combineReducers,我们先来分析下他干了啥,首先它的返回值是一个reducer,这个reducer同样会作为createStore的参数传进去,说明这个返回值是一个跟我们之前普通reducer结构一样的函数。这个函数同样接收state和action然后返回新的state,只是这个新的state要符合combineReducers参数的数据结构。我们尝试来写下:functioncombineReducers(reducerMap){constreducerKeysObject。keys(reducerMap);先把参数里面所有的键值拿出来返回值是一个普通结构的reducer函数constreducer(state{},action){constnewState{};for(leti0;ireducerKeys。length;i){reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值然后将所有reducer返回的state按照参数里面的key组装好最后再返回组装好的newState就行constkeyreducerKeys〔i〕;constcurrentReducerreducerMap〔key〕;constprevStatestate〔key〕;newState〔key〕currentReducer(prevState,action);}returnnewState;};returnreducer;}
  官方源码的实现原理跟我们的一样,只是他有更多的错误处理,大家可以对照着看下。手写applyMiddleware
  middleware是Redux里面很重要的一个概念,Redux的生态主要靠这个API接入,比如我们想写一个logger的中间件可以这样写(这个中间件来自于官方文档):logger是一个中间件,注意返回值嵌了好几层函数我们后面来看看为什么这么设计functionlogger(store){returnfunction(next){returnfunction(action){console。group(action。type);console。info(dispatching,action);letresultnext(action);console。log(nextstate,store。getState());console。groupEnd();returnresult}}}在createStore的时候将applyMiddleware作为第二个参数传进去conststorecreateStore(reducer,applyMiddleware(logger))
  可以看到上述代码为了支持中间件,createStore支持了第二个参数,这个参数官方称为enhancer,顾名思义他是一个增强器,用来增强store的能力的。官方对于enhancer的定义如下:typeStoreEnhancer(next:StoreCreator)StoreCreator
  上面的结构的意思是说enhancer作为一个函数,他接收StoreCreator函数作为参数,同时返回的也必须是一个StoreCreator函数。注意他的返回值也是一个StoreCreator函数,也就是我们把他的返回值拿出来继续执行应该得到跟之前的createStore一样的返回结构,也就是说我们之前的createStore返回啥结构,他也必须返回结构,也就是这个store:{subscribe,dispatch,getState}createStore支持enhancer
  根据他关于enhancer的定义,我们来改写下自己的createStore,让他支持enhancer:functioncreateStore(reducer,enhancer){接收第二个参数enhancer先处理enhancer如果enhancer存在并且是函数我们将createStore作为参数传给他他应该返回一个新的createStore给我我再拿这个新的createStore执行,应该得到一个store直接返回这个store就行if(enhancertypeofenhancerfunction){constnewCreateStoreenhancer(createStore);constnewStorenewCreateStore(reducer);returnnewStore;}如果没有enhancer或者enhancer不是函数,直接执行之前的逻辑下面这些代码都是之前那版省略n行代码。。。。。。。conststore{subscribe,dispatch,getState}returnstore;}
  这部分对应的源码看这里。applyMiddleware返回值是一个enhancer
  前面我们已经有了enhancer的基本结构,applyMiddleware是作为第二个参数传给createStore的,也就是说他是一个enhancer,准确的说是applyMiddleware的返回值是一个enhancer,因为我们传给createStore的是他的执行结果applyMiddleware():functionapplyMiddleware(middleware){applyMiddleware的返回值应该是一个enhancer按照我们前面说的enhancer的参数是createStorefunctionenhancer(createStore){enhancer应该返回一个新的createStorefunctionnewCreateStore(reducer){我们先写个空的newCreateStore,直接返回createStore的结果conststorecreateStore(reducer);returnstore}returnnewCreateStore;}returnenhancer;}实现applyMiddleware
  上面我们已经有了applyMiddleware的基本结构了,但是功能还没实现,要实现他的功能,我们必须先搞清楚一个中间件到底有什么功能,还是以前面的logger中间件为例:functionlogger(store){returnfunction(next){returnfunction(action){console。group(action。type);console。info(dispatching,action);letresultnext(action);console。log(nextstate,store。getState());console。groupEnd();returnresult}}}
  这个中间件运行效果如下:
  可以看到我们letresultnext(action);这行执行之后state改变了,前面我们说了要改变state只能dispatch(action),所以这里的next(action)就是dispatch(action),只是换了一个名字而已。而且注意最后一层返回值returnfunction(action)的结构,他的参数是action,是不是很像dispatch(action),其实他就是一个新的dispatch(action),这个新的dispatch(action)会调用原始的dispatch,并且在调用的前后加上自己的逻辑。所以到这里一个中间件的结构也清楚了:
  一个中间件接收store作为参数,会返回一个函数返回的这个函数接收老的dispatch函数作为参数,会返回一个新的函数返回的新函数就是新的dispatch函数,这个函数里面可以拿到外面两层传进来的store和老dispatch函数
  所以说白了,中间件就是加强dispatch的功能,用新的dispatch替换老的dispatch,这不就是个装饰者模式吗?其实前面enhancer也是一个装饰者模式,传入一个createStore,在createStore执行前后加上些代码,最后又返回一个增强版的createStore。可见设计模式在这些优秀的框架中还真是广泛存在,如果你对装饰者模式还不太熟悉,可以看我之前这篇文章。
  遵循这个思路,我们的applyMiddleware就可以写出来了:直接把前面的结构拿过来functionapplyMiddleware(middleware){functionenhancer(createStore){functionnewCreateStore(reducer){conststorecreateStore(reducer);将middleware拿过来执行下,传入store得到第一层函数constfuncmiddleware(store);解构出原始的dispatchconst{dispatch}store;将原始的dispatch函数传给func执行得到增强版的dispatchconstnewDispatchfunc(dispatch);返回的时候用增强版的newDispatch替换原始的dispatchreturn{。。。store,dispatch:newDispatch}}returnnewCreateStore;}returnenhancer;}
  照例用我们自己的applyMiddleware替换老的,跑起来是一样的效果,说明我们写的没问题,哈哈
  支持多个middleware
  我们的applyMiddleware还差一个功能,就是支持多个middleware,比如像这样:applyMiddleware(rafScheduler,timeoutScheduler,thunk,vanillaPromise,readyStatePromise,logger,crashReporter)
  其实要支持这个也简单,我们返回的newDispatch里面依次的将传入的middleware拿出来执行就行,多个函数的串行执行可以使用辅助函数compose,这个函数定义如下。只是需要注意的是我们这里的compose不能把方法拿来执行就完了,应该返回一个包裹了所有方法的方法。functioncompose(。。。func){returnfuncs。reduce((a,b)(。。。args)a(b(。。。args)));}
  这个compose可能比较让人困惑,我这里还是讲解下,比如我们有三个函数,这三个函数都是我们前面接收dispatch返回新dispatch的方法:constfun1dispatchnewDispatch1;constfun2dispatchnewDispatch2;constfun3dispatchnewDispatch3;
  当我们使用了compose(fun1,fun2,fun3)后执行顺序是什么样的呢?第一次其实执行的是(func1,func2)(。。。args)func1(fun2(。。。args))这次执行完的返回值是下面这个,用个变量存起来吧consttemp(。。。args)func1(fun2(。。。args))我们下次再循环的时候其实执行的是(temp,func3)(。。。args)temp(func3(。。。args));这个返回值是下面这个,也就是最终的返回值,其实就是从func3开始从右往左执行完了所有函数前面的返回值会作为后面参数(。。。args)temp(func3(。。。args));再看看上面这个方法,如果把dispatch作为参数传进去会是什么效果(dispatch)temp(func3(dispatch));然后func3(dispatch)返回的是newDispatch3,这个又传给了temp(newDispatch3),也就是下面这个会执行(newDispatch3)func1(fun2(newDispatch3))上面这个里面用newDispatch3执行fun2(newDispatch3)会得到newDispatch2然后func1(newDispatch2)会得到newDispatch1注意这时候的newDispatch1其实已经包含了newDispatch3和newDispatch2的逻辑了,将它拿出来执行这三个方法就都执行了
  更多关于compose原理的细节可以看我之前这篇文章。
  所以我们支持多个middleware的代码就是这样:参数支持多个中间件functionapplyMiddleware(。。。middlewares){functionenhancer(createStore){functionnewCreateStore(reducer){conststorecreateStore(reducer);多个middleware,先解构出dispatchnewDispatch的结构constchainmiddlewares。map(middlewaremiddleware(store));const{dispatch}store;用compose得到一个组合了所有newDispatch的函数constnewDispatchGencompose(。。。chain);执行这个函数得到newDispatchconstnewDispatchnewDispatchGen(dispatch);return{。。。store,dispatch:newDispatch}}returnnewCreateStore;}returnenhancer;}
  最后我们再加一个logger2中间件实现效果:functionlogger2(store){returnfunction(next){returnfunction(action){letresultnext(action);console。log(logger2);returnresult}}}letstorecreateStore(reducer,applyMiddleware(logger,logger2));
  可以看到logger2也已经打印出来了,大功告成。
  现在我们也可以知道他的中间件为什么要包裹几层函数了:第一层:目的是传入store参数
  第二层:第二层的结构是dispatchnewDispatch,多个中间件的这层函数可以compose起来,形成一个大的dispatchnewDispatch
  第三层:这层就是最终的返回值了,其实就是newDispatch,是增强过的dispatch,是中间件的真正逻辑所在。总结单纯的Redux只是一个状态机,store里面存了所有的状态state,要改变里面的状态state,只能dispatchaction。对于发出来的action需要用reducer来处理,reducer会计算新的state来替代老的state。subscribe方法可以注册回调方法,当dispatchaction的时候会执行里面的回调。Redux其实就是一个发布订阅模式!Redux还支持enhancer,enhancer其实就是一个装饰者模式,传入当前的createStore,返回一个增强的createStore。Redux使用applyMiddleware支持中间件,applyMiddleware的返回值其实就是一个enhancer。Redux的中间件也是一个装饰者模式,传入当前的dispatch,返回一个增强了的dispatch。单纯的Redux是没有View层的,所以他可以跟各种UI库结合使用,比如reactredux,计划下一篇文章就是手写reactredux。

全与智能领先同级,10万级纯电动车超值首选比亚迪e2当之无愧不限牌、不限行,免税还能拿补贴,纯电动车越来越受欢迎。在10万级纯电市场中,作为超安全纯电精品的2021款比亚迪e2,凭借出色的安全品质和高性价比优势,一直是同级市场中当之无愧……清除手机垃圾让手机更顺畅不再卡机你是否因你的手机内存不足,导致你手机太卡速度很慢呢?甚至有时手机卡到无法打开呢?下面把清除手机垃圾的几个办法送给你,请收藏以便忘随时能到收藏里查看,不收藏可就找不到了哟!……央视小撒探秘的黑灯工厂,有多炫酷?近日央视主持人撒贝宁来到老板电器茅山基地做一日厂长初来乍到的他召集员工开会结果却无人到场这是怎么回事呢?让我们一起看看小撒的新晋厂长之……外媒中国科技监管下,App数量三年减四成来源:环球时报马来西亚《星报》12月21日文章,原题:中国的科技监管:由于新的数据安全法和净网行动,应用程序三年减少四成中国的智能手机用户发现,过去三年他们可用的应用程序……投简历登陆求职网站被公司监控?深信服官网已检索不到行为感知系每经记者:王晶每经编辑:魏官红近日,网上流传的一张系统后台图片显示,在行为感知系统内,公司可查看有离职倾向员工的详细情况,一名员工访问求职网站23次,投递简历9次,含关键……手机硬件超出switch很多,为什么做不出高质量的单机游戏?因为手机(尤其是安卓)如果root,能免费下载所有游戏,另外不root也能找到一些破解游戏。也就是说在手机上做单机游戏很可能赚不到钱,还不如随便出个网游,即使是换皮或者盗……小米有品百元空调,1s制冷100档风速,租房党太顶了随着科技的发展,人们也有越来越多的科技手段来面对各种各样的情况。比如即将到来的夏天,基本上都会在空调里度过,不过对于小编这样有轻微鼻炎的人来说,长时间待在空调里实在是太辛苦了。……vivo新专利曝光让手机与无人机真正融合手机与无人机,可以说是两个性质不同的产品,除了都可以拍摄用来拍摄外,唯一的关联就是智能手机可以当作遥控器来操作无人机。近日,国产手机品牌vivo的一项手机专利曝光,让智能手机和……OPPO又一生物识别技术曝光,有望提升人脸解锁效率,网友这样随着全面屏发展趋势的推进,如今大部分的智能手机都是采用了刘海屏或者挖孔屏设计,而解锁方案也基本都是采用人脸识别这样的活体生物方式。不过在这方面,其实也存在着不少的问题,比如说解……颜值巅峰!荣耀50系列荣耀密码配色正式开售,2699元起9月3日,荣耀50系列于此前宣布推出的全新荣耀密码配色正式开售,售价2699元起。01hr手机配色最近,荣耀50系列又推出了新配色荣耀密码。一经推出即刻封顶,受到众……iOS15第一个测试版Bug大汇总,附降级教程在本周二举行的苹果WWDC21发布会上,苹果正式推出了今年第一个大版本测试版iOS15Beta1,该版本正式版也将在今年九月份苹果秋季发布会上伴随iPhone13一并推送。……testflight小白详细教程看过来testflight小白详细教程微信公众号xo科技Testflight开发者2021072212:03小白详细教程考虑到有很多不明所以的用户不明白怎么使用和安……
设计厂Q4涨价计划曝光联发科上调幅度高至30业内消息称,中国台湾几家IC设计公司准备从2021年第四季度开始提高芯片价格,以反映制造成本的上升。《电子时报》报道援引上述人士称,联发科、DDI巨头联咏、网络芯片大厂瑞……小班音乐教案(通用7篇)作为一位优秀的人民教师,编写教案是必不可少的,教案是教学活动的依据,有着重要的地位。写教案需要注意哪些格式呢?下面是小编收集整理的小班音乐教案(通用7篇),欢迎阅读,希望大家能……继英雄联盟手游后,微信再屏蔽王者荣耀周年庆活动分享感谢IT之家网友伟大的艺术家的线索投递!IT之家10月17日消息,《英雄联盟手游》最近开启了公测,并推出了英雄重逢,峡谷新程分享免费获得皮肤英雄的活动。但由于分享举报过多……身价是巴菲特两倍多,马斯克调侃买特斯拉公司股票吧10月19日消息,特斯拉和SpaceX首席执行官埃隆马斯克(ElonMusk)最近发文,调侃有股神美誉的知名投资人沃伦巴菲特(WarrenBuffett)比他穷。马斯克暗示,如……涉嫌超范围采集个人隐私信息,京东读书腾讯手游加速器等14款违IT之家10月20日消息,据新华社报道,国家计算机病毒应急处理中心近期通过互联网监测发现14款移动应用存在隐私不合规行为,违反《网络安全法》相关规定,涉嫌超范围采集个人隐私信息……消息称台积电联电明年报价再涨2010,但代工涨价整体趋缓代工厂消息人士表示,晶圆代工报价预计将继续上涨。其中,台积电12月后或将调涨20,而联电已通知客户,明年1月起产品价格将再次上调不到10。据《电子时报》援引上述人士称,虽……语文难忘的泼水节说课稿大家好,我今天说课的题目是《难忘的泼水节》,我的说课内容分成六部分:教材分析、教学目标、学生情况、教法学法、教学过程、板书设计。一、教材分析1、《难忘的泼水节》是新……高通年度技术峰会时间安排公布,骁龙865将亮相IT之家11月20日消息根据高通官方的消息,高通将在夏威夷标准时间12月3日4日5日每日上午9:00(北京时间12月4日5日6日每日清晨3:00)举行骁龙年度技术峰会主题演讲,……微软决定与美国社区大学合作,未来培养25万名网络安全从业者IT之家10月29日消息,根据外媒mspoweruser报道,随着网络攻击事件的增多,美国目前急需网络安全从业者,这一个岗位的空缺达到了46。5万个。微软警告称,美国正受到越来……世粮署执行主任马斯克贝索斯等富豪应捐出部分资产缓解全球饥饿10月27日消息,据国外媒体报道,联合国世界粮食计划署执行主任大卫比斯利呼吁亿万富翁埃隆马斯克和杰夫贝索斯捐出他们净资产的0。36,帮助拯救全球4200万面临严重饥饿问题的人。……四年级火烧云的教学反思范文一、重视导读,在读中锤炼语言美《火烧云》一文是著名女作家萧红写的。课文描写了日落时晚霞的美丽景象,全文以变字统领全篇,且节节有变,使自然之美、人与物之美在变中表现得淋漓尽……联想杨元庆年薪1。8亿,位居2020年董事长薪酬榜第39位近日,随着联想终止科创板上市,柳传志退休后年薪1亿、杨元庆年薪高于库克的消息满天。为此,联想控股特意发表声明消息失实。公开信息显示,今年(2021年)1月12日,联想集团……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网