redux、koa、快递中间件对比分析
如果你有使用express、koa、redux的经验,你会发现他们都有中间件的概念。中间件是一种拦截器思想,用于在特定的输入和输出之间增加一些额外的处理,而不影响原始操作。
第一次接触中间件是在服务器端使用express和koa的时候,然后从服务器端扩展到前端,看到它在redux的设计中也起到了很大的作用。中间件的设计思想也给很多框架带来了灵活而强大的可扩展性。
本文主要比较了redux、koa、express的中间件实现。为了更直观,我将提取三个中间件的相关核心代码,进行简化并编写仿真示例。示例将保留express、koa和redux的整体结构,并尽量与源代码保持一致,因此本文也将说明Express、KOA和Redux的整体结构和关键实现:
示例源码,可以在阅读文章的同时阅读源代码。欢迎来到星空!
本文适合对express、koa、redux有一定了解和经验的开发者
服务器端中间件express和koa中间件用于处理http请求和响应,但它们的设计思路并不相同。大多数人知道的express和koa之间的中间件差异如下:
Express采用“尾部递归”模式,中间件依次逐一执行,用于在最后一个中间件中写入响应响应;koa的中间件支持生成器,执行顺序为洋葱环模型。所谓“洋葱圈”型号:
但是实际上,express的中间件也可以形成一个“洋葱圈”模型,下一个调用之后写的代码也会被执行,但是这在express中一般是做不到的,因为express的响应一般在最后一个中间件中,所以其他中间件的next()之后的代码就不能再影响最终的响应结果;
表达
我们先来看看express的实现:
入口
//express.jsvar proto=require('。/application’);var mixin=require(' merge-descriptor ');exports=module . exports=create application;函数createapplication () {//app也是方法,作为http . create server var app=function(req,RES,next) {app.handle (req,RES,next)} mixin (app,proto,false)的处理函数;这里的Return app}其实很简单,就是用一个createApplication方法创建一个express实例。注意返回值app不仅仅是一个实例对象,上面挂载了很多方法,还是一个方法本身,作为http.createServer的处理函数,具体代码在application.js:
//application . jsvar http=require(' http ');var flat=required(' array-flat ');var app=exports=module . exports={ } app.listen=function listen(){ var server=http . createserver(this)return server . listen . apply(server,Arguments)}此处app . listen调用nodejs' http.createServer创建web服务。你可以在这里看到varserver=http。createserver (this)这里是app本身,然后真正的处理程序是app.handle
中间件处理
Express本质上是一个中间件管理器。当你进入app.handle的时候,就是执行中间件的时候了。因此,两个最关键的功能是:
app.handle的末尾递归调用中间件来处理req和res应用程序。使用Add中间件来维护一个堆栈数组,用于全局存储所有中间件。app.use的实现非常简单,可以是一行代码。
//app . usepp . use=function(fn){ this . stack . push(fn)} Express的真正实现当然没有那么简单。它内置了路由功能,包括三个关键类:路由器、路由、层。使用路由器时,有必要分割路径。层实例保存在栈中,app.use方法实际调用路由器实例的use方法,有兴趣的可以自己读。
App.handle处理堆栈数组
app.handle=function(req,res,callback){ var stack=this . stack;var idx=0;函数next(err){ if(idx=stack . length){ callback(' err ')返回;} var midwhile(idx stack . length){ mid=stack[idx];mid(请求、资源、下一个);}} next()}这里是所谓的‘尾递归调用’。下一个方法不断取出堆栈中的中间件函数并调用它们,同时将next本身作为第三个参数传递给中间件。每个中间件契约的固定形式是(req,res,next)={},这样每个中间件函数只要调用下一个方法,就可以转移和调用下一个中间件。
之所以称之为“尾部递归”,是因为递归函数的最后一条语句调用自身,所以每个中间件的最后一条语句都需要是next()才能形成“尾部递归”,否则就是普通递归。与普通递归相比,“尾递归”具有节省内存空间、不形成深嵌套函数调用栈的优点。有兴趣的可以看看夏阮的尾号优化
至此,express的中间件实现完成。
寇阿相思树
不得不说,与express相比,koa的整体设计和代码实现更加先进和精细;代码基于ES6实现,支持生成器(async await),没有内置路由实现和任何内置中间件,上下文的设计也很巧妙。
全部的
只有四份文件:
application.js入口文件是koa应用程序实例的一个类context.js ctx实例,它代表了请求和响应的许多属性和方法。将request.js koa作为全局对象传递封装了本机req对象。response.js koa封装了本机res对象。关于request.js和response.js没什么好说的,任何web框架都会提供req和res的封装,以简化处理。所以主要看context.js和application.js的实现
//context.js /** *响应委托。*/delegate(proto,' res ')。方法(' setHeader')/** *请求委托。*/delegate(proto,' req ')。访问(' url ')。setter('href ')。getter(' IP ');上下文就是这种代码,它的主要功能是充当代理,使用委托库。
在这里简单解释一下代理的含义,例如,语句委托(proto,‘RES’)的功能。方法(' setHeader ')是在调用proto.setHeader的时候,会调用proto.res.setHeader,也就是proto的setHeader方法会代理到proto的res属性,以及其他类似的。
部分代码构造函数(){ super()this . middleware=[]this . context=object . create(context)} use(fn){ this . middleware . push(fn)} listen(.args){ debug(' listen ')const server=http . createserver(this . callback());return server.listen(.args);}回调(){//这里是中间件处理代码constfn=compose(这个。中间件);Const handlerequest=(req,res)={//ctx是koa的精髓之一。许多应请求的方法、资源被委托给ctx,基于CTX处理许多问题更方便。CTX常量=这个。createcontext (req,RES);返回this.handleRequest(ctx,fn);};返回handleRequest}handleRequest(ctx,fnMiddleware){ CTX . statuscode=404;const one rror=err=CTX . one rror(err);const handleResponse=()=response(CTX);返回fnMiddleware(ctx)。然后(handleResponse)。catch(one rror);web服务是以同样的方式在listen方法中创建的,而不是使用express,const server=http . create server(this . callback());使用this.callback()为web服务生成处理程序
回调函数返回handleRequest,所以真正的处理程序是这样的
中间件处理
构造函数维护全局中间件数组this.middleware和全局this.context实例(源代码中还有请求、响应对象和一些其他辅助属性)。与express不同,由于没有路由器实现,所有这些中间件都是普通的“中间件”功能,而不是复杂的层实例。
this.handleRequest(ctx,fn);其中ctx是第一个参数,fn=compose(this.middleware)是第二个参数,handleRequest将调用fn中间件(CTX)。然后(handleresponse)。catch(one rror);因此,中间处理的关键在于compose方法,它是一个独立的包koa-compose。拿出来看看里面的内容:
//compose . js ' use strict ' module . exports=compose function compose(中间件){ return function (context,next){ let index=-1 return dispatch(0)function dispatch(I){ index=I let fn=中间件[i] if (i===中间件. length) fn=next if(!fn)返回Promise.resolve()尝试{ return promise . resolve(fn(context,dispatch.bind(null,I ^ 1));} catch (err) {return诺言。reject (err)}}}与express中的next类似,只是形式为promise,所以理解起来有点麻烦,因为它支持async (ctx,next)=}。执行后返回一个promise,第二个参数next的值为dispatch.bind(null,i 1),用于传递“中间件”的执行,每个中间件向内执行,直到最后一个中间件执行并解析,然后前面的“中间件”在wait next()后执行代码。然后解决,继续前进,直到第一个“中间件”解决,最后做出最外层的承诺解决。
这里express和koa的区别在于,KOA的响应不是在“中间件”中处理的,而是在中间件执行返回的promise resolve之后:
返回fnMiddleware(ctx)。然后(handleResponse)。catch(one rror);
响应最终通过handleResponse进行处理。中间件会设置ctx.body,handleResponse主要处理ctx.body,所以会建立koa的洋葱圈模型,wait next()之后的代码也会影响最终的响应。
至此,koa的中间件实现完成。
回家的
不得不说redux的设计思路和源代码实现真的很漂亮,整体代码量小。redux的源代码分析在网上随处可见,我就不细说了。不过,还是有必要推荐一波官方网站对中间件: redux-中间件的叙述
这是我看过的最好的解释性文档,没有一篇清晰地说明了redux中间件的演进过程,漂亮地诠释了一个从分析问题到解决问题并不断优化的思维过程。
全部的
本文主要研究它的中间件实现。首先简单说说redux的核心处理逻辑。createStore是它的入口程序,一个工厂方法,并返回一个存储实例。存储实例最关键的方法是调度,而调度必须做一件事:
电流状态=电流减少器(电流状态,动作)
即调用reducer,传入当前状态和动作,返回新状态。
因此,要模拟基本的redux执行,只需实现createstore和dispatch方法。其他内容,比如BindActionCreators、CombineReducers、订阅监控等都是辅助功能,暂时可以忽略。
中间件处理
然后来到核心的“中间件”实现部分,即applyMiddleware.js:
//applyMiddleware.jsimport从'撰写。/compose '导出默认函数applyMiddleware(.middleware){ return CreateStore=(.args)={ const store=createStore(.args)让dispatch=()={抛出新错误(`不允许在构建中间件时进行调度。` `其他中间件不会应用于此调度。`) } const middlewareAPI={ getstate : store . getstate,dispatch:(.args)=调度(.args)} const chain=middleware . map(中间件=中间件(middleware API))dispatch=compose(.连锁(商店.配送)退货.store,dispatch } } } Redux中间件提供的扩展是,它的实现思路与express和koa在动作发起后,到达reducer之前的实现思路有些不同。它没有通过存储的封装。派遣。中间件处理程序被添加在它的前面,但是它是通过递归重写调度和连续传递最后一个被重写的调度来实现的。
每个redux中间件的形式是store=next=action={xxx}
函数嵌套主要有两个层次:
最外层的函数接收参数存储,对应的是applyMiddleware.js中的处理代码是constchain=中间件s.map(中间件=中间件API),中间件API是传入的存储。这一层是将存储的api传递给中间件,主要是两个api:
GetState,直接传递store.getstate.dispatch:(.args)=调度(.args),这里的实现非常巧妙,它不是store.dispatch,而是一个外部变量dispatch,最终指向被覆盖的dispatch。这样做的原因是,对于redux-thunk这样的异步中间件,在内部调用store.dispatch时,所有的“中间件”仍然会经过。返回的链是第二层的数组,数组的每个元素都是next=action={xxx}这样的函数。这个函数可以理解为接受一个调度并返回一个调度,接受的调度就是后者中间件返回的调度。
还有一个关键函数,即compose,其主要功能是返回()=f(g(h(.args)))
现在比较容易理解dispatch=compose(.连锁(商店.配送)。原店。调度被引入到最后一个“中间件”中,返回一个新的调度,然后将它传递给前面的中间件,直到返回最终的调度。当调用被覆盖的调度时,每个“中间件”的执行来自
至此,redux中间件完成。
其他要点
redux中间件的实现还有一点值得学习。为了使“中间件”只能应用一次,applyMiddleware并不作用于存储实例,而是作用于createStore工厂方法。怎么理解?如果applyMiddleware是这样的,
(商店、中间商)={}
那么当应用中间件(商店、中间件)被多次调用时,同样的中间件会被重复添加到同一个实例中。所以applyMiddleware的形式是
(.middleware)=(createStore)=createStore,
这样,每次应用中间件都会创建一个新的实例,避免了中间件重复应用的问题。
该表单将接收中间件返回的高级方法createStore。这种方法一般称为createStore的增强方法,内部增加了中间件的应用。您会发现这种方法与中间件的第二层(dispatch)=dispatch是一致的,因此它也可以在compose中用于多个增强。同时,createStore还有第三个参数增强,用于内部判断和自我增强。因此,有两种方法可以使用redux中间件:
第一个:使用applyMiddleware返回增强和增强createStore
Store=apply中间件(中间件1,中间件2)(createStore)(reductor,init state)第二个: createStore接收一个增强器参数用于自我增强
store=createstore(reductor,init state,apply middleware(中间件1,中间件2))第二种用法会更简单易懂。
在redux的整个实现过程中,函数式编程体现得淋漓尽致。中间件表单存储=next=action={xx}是功能核心化的灵活体现。它可以用来预先固定存储参数,得到更明确的调度=dispatch,使compose发挥作用。
摘要
一般来说,express和koa的实现非常相似,都是用next方法递归调用,只是koa是promise的形式。Redux与前两者略有不同,前者首先被递归覆盖,然后在执行过程中被递归向内调用。
总结三者的主要异同(不限于中间件):
示例创建:express使用工厂方法,koa是类koa,语法更高级。它使用ES6,并支持生成器(异步感知)。KOA没有内置路由器,加入了ctx全局对象,使得整个代码更简单,使用更方便。koa中间件的递归是promise的形式,而loop和next tail递归在express中使用。我比较喜欢redux的实现,是coreization中间件的形式,比较简洁灵活。函数式编程更为明显。redux以调度覆盖的形式增强了中间件。最后,再次附上仿真例子的源代码,供学习参考。欢迎明星,叉子!
回答一个问题。
有人说express也可以用async函数作为中间件进行异步处理?实际上是不可能的,因为express的中间件执行是同步while循环。当中间件既包含普通功能又包含异步功能时,执行顺序就会混乱。让我们举个例子:
函数a () {console.log ('a')}异步函数b(){ console . log(a ' ' b ' ' f ' ' c await 1 console . log(' c ')await 2 console . log(' d ')}函数f(){ a()(b)()console . log(' f
在普通函数中直接调用异步函数,异步函数会在第一次等待后同步执行代码,然后立即返回承诺。当异步函数中的所有等待异步完成时,承诺将被解析。
所以,通过上面对express中间件实现的分析,如果异步函数作为中间件,内部使用await进行异步处理,那么首先执行下面的中间件,再次调用await时会超过下一个索引!可以在这里打开评论express async,自己试试。
以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。
版权声明:redux、koa、快递中间件对比分析是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。