当前位置: 首页 > Web前端 > HTML

web前端培训Nestjs模块机制概念与实现原理

时间:2023-03-28 00:59:20 HTML

Web前端实训Nestjs模块机制概念及实现原理以及provider构造器完成依赖注入,通过模块树组织整个应用的开发。按照框架本身的约定,直接启动一个应用是完全没有问题的。然而,对于我来说,我觉得对框架声明的依赖注入、控制反转、模块、提供者、元数据、相关装饰器等缺乏更清晰系统的认识。我好像能看懂,能感觉到,但是让我从头解释清楚,我解释不清楚。所以我做了一些研究并提出了这篇文章。从现在开始,我们重新开始,输入文字。2两个阶段2.1Express、Koa、一门语言及其技术社区的发展过程,必须从底层功能逐步丰富和发展,就像一棵树的根慢慢长成枝条,再长满树叶的过程。早些时候,Nodejs与Express和Koa等基本Web服务框架一起出现。能够提供非常基础的服务能力。基于这样的框架,社区中诞生了大量的中间件和插件,为框架提供更丰富的服务。我们需要自己整理应用依赖,搭建应用脚手架,灵活且繁琐,需要一定的工作量。经过发展,一些生产效率更高、规则更统一的框架诞生了,开启了一个新的阶段。2.2EggJs、Nestjs为了更适??合快速生产应用、统一规范、开箱即用,开发了EggJs、NestJs、Midway等框架。这种框架通过底层生命周期的实现,将应用的实现抽象为一个通用的、可扩展的过程。我们只需要按照框架提供的配置方法,就可以更简单的实现应用。框架实现了程序的流程控制,我们只需要将我们的部分组装到合适的地方即可。这看起来更像是一个流水线工作,每个过程都划分的很清楚,而且节省了大量的实施成本_前端培训。2.3总结以上两个阶段只是一个铺垫。我们可以大致理解为框架的升级提高了生产效率,而要实现框架的升级,会引入一些设计思想和模式。控制反转出现在Nest、依赖注入、元编程等概念中,下面就来聊一聊。3控制反转与依赖注入3.1依赖注入一个应用其实就是很多抽象类,它们通过相互调用来实现应用的所有功能。随着应用代码和功能复杂度的增加,项目会越来越难维护,因为类越来越多,它们之间的关系越来越复杂。比如我们使用Koa来开发我们的应用,Koa本身主要实现了一套基本的Web服务能力。在实现应用程序的过程中,我们会定义很多类。这些类的实例化方法和相互依赖关系,都将在代码逻辑上由我们自由组织和控制。每个类的实例化都是我们手动new的,我们可以控制某个类是只实例化一次然后共享,还是每次都实例化。下面的B类依赖于A,B每被实例化一次,A就会被实例化一次,所以对于每一个实例B来说,A都是一个非共享实例。classA{}//BclassB{contructor(){this.a=newA();}}下面的C是获取到的外部实例,所以多个C实例共享app.a实例。classA{}//Cconstapp={};app.a=newA();classC{contructor(){this.a=app.a;}}下面的D是通过constructor参数传入的,你可以每次传入一个非共享的实例,也可以传入一个共享的app.a实例(D和F共享app.a),而且因为是作为参数传入的,所以也可以传入一个X类实例。classA{}classX{}//Dconstapp={};app.a=newA();classD{contructor(a){this.a=a;}}classF{contructor(a){this.a=a;}}newD(app.a)newF(app.a)newD(newX())这个方法就是依赖注入,将B所依赖的A注入到B中。注入(传值)通过构造函数只是一种实现方式,也可以通过调用set方法的方式传入,也可以通过其他方式传入,只要能向内部传入外部依赖即可。其实就是这么简单。classA{}//DclassD{setDep(a){this.a=a;}}constd=newD()d.setDep(newA())3.2全部依赖注入?随着迭代的进行,B似乎会根据不同的前提条件依赖性而发生变化。比如前置条件一this.a需要传入一个A的实例,前置条件二this.a需要传入一个X的实例。这时候我们就要开始做真正的抽象了。我们将其改造为像上面D那样的依赖注入方式。前期我们在实现应用的时候,会在满足当时需求的情况下实现B和C的写法。本身没有问题。项目迭代几年后,这部分代码可能不会动。.如果考虑到后期扩展什么的,会影响开发效率,也未必有用。所以很多时候,我们会遇到需要抽象的场景,然后对一些代码进行抽象化改造。//改造前classB{contructor(){this.a=newA();}}newB()//改造后classD{contructor(a){this.a=a;}}newD(newA())newD(newX())按照目前的开发模型,CBD的三种类型都会存在,B和C有一定的概率发展成D。每次升级D的抽象过程,我们需要重构代码。这是实施成本。这里举这个例子是为了说明在没有任何约束和规定的开发模式下。我们可以自由编写代码来实现各个类之间的依赖控制。在完全开放的环境中,是非常自由的。这是刀耕火种的原始时代。由于没有固定的代码开发模型,也没有最高的行动纲领,随着不同开发人员的介入,或者同一开发人员在不同时间段编写的代码的差异,在代码增长过程中依赖关系会变得非常不同.显然,共享实例可能会被多次实例化,从而浪费内存。从代码中很难看到完整的依赖结构,代码会变得非常难以维护。然后我们每次定义一个类的时候,都是用依赖注入的方式写的,写成D,这样就提前了C和B的抽象过程,这样后面的扩展更方便,也降低了改造成本。所以这个叫做Allindependencyinjection,也就是我们所有的依赖都是通过依赖注入来实现的。但这样一来,前期的实施成本又变高了,很难做到团队合作的团结和坚持,最终可能落地失败。这也可以定义为过度设计,因为额外的实施成本不一定能带来收益。3.3控制反转既然已经约定了依赖注入的统一使用,那么是否可以通过框架底层封装实现一个底层controller,约定一个依赖配置规则,controller根据我们定义的依赖配置与依赖共享帮助我们实现类管理。这种设计模式称为控制反转。第一次听到控制反转可能很难理解。控制是什么意思?什么是反转?猜测是开发者从一开始就用过这样的框架,没有经历过上次的“Express、Koa时代”,少了旧社会的毒打。再加上这种反写,在程序中就显得很抽象,很难读懂字面意思。正如我们上面所说的实现Koa应用,所有的类都完全在我们的自由控制之下,所以可以看作是一种常规的程序控制方式,所以称之为:控制前移。而我们使用的是Nest,它在底层实现了一套控制器。我们在实际开发过程中只需要按照约定编写配置代码,框架程序会帮我们管理类的依赖注入,所以称之为:InversionofControl。本质是将程序的执行过程交给框架程序统一管理,控制权从开发者转移到框架程序。控制正转:纯开发者手动控制控制反转:框架程序控制举一个现实的例子,一个人本来是自己开车上班,他的目的是为了到达公司。它驱动自己并控制自己的路线。而如果交出驾驶权,他就要去赶公交车,只需要选择相应的公交车就可以到达公司。单从控制的角度来说,人是解放的。他们只需要记得乘公共汽车。犯错的几率也小,人也轻松很多。总线系统是控制器,总线是约定的配置。通过上面的实际对比,我想我应该可以对控制反转有一点了解了。3.4小结从Koa到Nest,从前端JQuery到VueReact。其实都是通过框架封装一步一步来解决以前时代效率低下的问题。上面的Koa应用开发采用了非常原始的方式来控制依赖和实例化,类似于前端的JQuery操作dom。这种很原始的方式叫做controlforward,而VueReact就像Nest提供了一层程序控制器,它们都可以叫做inversionofcontrol。这也是我个人的理解。有问题还望高手指点_web前端培训。接下来说一下Nest中的模块@Module,它是依赖注入和控制反转所需要的媒介。4Nestjs模块(@Module)Nestjs实现了控制反转,配置模块(@module)的imports、exports、providers约定管理provider,也就是类的依赖注入。Provider可以理解为在当前模块中注册和实例化类。在当前模块中实例化了下面的A和B。如果B在构造函数中引用A,则它是当前ModuleD的引用A实例。从'@nestjs/common'导入{Module};从'./moduleX'导入{ModuleX};从'./A'导入{A};从'./B'导入{B};@Module({imports:[ModuleX],providers:[A,B],exports:[A]})exportclassModuleD{}//BclassB{constructor(a:A){this.a=a;}}exports是导出当前模块providers中实例化的类,作为可以被外部模块共享的类。比如在实例化ModuleF的C类时,想直接注入ModuleD的A类实例。只需在ModuleD中设置export(导出)A,在ModuleF中通过imports导入ModuleD即可。按照下面的写法,反转控制程序会自动扫描依赖项。首先查看自己模块的providers中是否有providerA。如果没有,检查导入的ModuleD中是否有A的实例。如果存在,则获取ModuleD的A。注入到C实例中的实例。从'@nestjs/common'导入{Module};从'./moduleD'导入{ModuleD};从'./C'导入{C};@Module({imports:[ModuleD],providers:[C],})exportclassModuleF{}//CclassC{constructor(a:A){this.a=a;}}因此,如果要让外部模块使用当前模块的类实例,首先要定义instance在当前模块类的providers中,然后定义并export这个类,否则会报错。//正确的@Module({providers:[A],exports:[A]})//错误的@Module({providers:[],exports:[A]})这里要提一下ts的知识点导出类C{constructor(privatea:A){}}由于TypeScript支持构造函数参数(private、protected、public、readonly)隐式自动定义为类属性(ParameterProperty),所以不需要使用this.a=A。Nest中是这样写的。5Nest元编程元编程的概念体现在Nest框架中,它的控制反转和装饰器是元编程的实现。可以大致理解为,元编程的本质还是编程,只是中间有一些抽象的程序。这个抽象程序可以识别元数据(比如@Module中的对象数据),这实际上是一种扩展能力,可以将其他程序作为数据使用。去处理。我们在写这样一个抽象的程序,就是元编程。5.1元数据元数据在Nest文档中经常被提及。第一次看到元数据的概念会比较难理解。元数据的定义是:数据描述数据,主要是描述数据属性的信息,也可以理解为数据描述程序。Nest中@Module配置的exports、providers、imports、controllers都是元数据,因为它是用来描述程序关系的数据。这些数据信息并不是展示给最终用户的实际数据,而是被框架程序读取识别出来的。5.2Nest装饰器如果你查看Nest中装饰器的源代码,你会发现几乎每个装饰器本身都只通过reflect-metadata定义了一个元数据。@Injectable装饰器导出函数Injectable(options?:InjectableOptions):ClassDecorator{return(target:object)=>{Reflect.defineMetadata(INJECTABLE_WATERMARK,true,target);Reflect.defineMetadata(SCOPE_OPTIONS_METADATA,options,target);};}这里是反射的一个概念,反射比较容易理解。以@Module装饰器为例,定义元数据提供者,但只是将类传递到提供者数组中。当程序真正运行时,providers中的类会被框架程序自动实例化为provider,不需要开发者显式进行实例化和依赖注入。只有在模块中实例化后,类才能成为提供者。提供者中的类被反映为提供者,控制反转是使用的反射技术。另一个例子是数据库中的ORM(对象关系映射)。使用ORM,只需要定义表字段,ORM库会自动将对象数据转换成SQL语句。constdata=TableModel.build();data.time=1;data.browser='chrome';data.save();//SQL:INSERTINTOtableName(time,browser)[{"time":1,"browser":"chrome"}]ORM库使用反射技术,让用户只需要关注字段数据本身,对象通过ORM库反射成为SQL执行语句。开发者只需要关注数据字段,而不需要写SQL了。5.3reflect-metadatareflect-metadata是Nest用来管理元数据的反射库。reflect-metadata使用WeakMap创建全局单实例,通过set和get方法设置和获取被装饰对象(类、方法等)的元数据。//看看var_WeakMap=!usePolyfill&&typeofWeakMap==="function"?WeakMap:CreateWeakMapPolyfill();varMetadata=new_WeakMap();functiondefineMetadata(){OrdinaryDefineOwnMetadata(){GetOrCreateMetadataMap(){vartargetMetadata=Metadata.get(O);if(IsUndefined(targetMetadata)){if(!Create)返回未定义;targetMetadata=new_Map();Metadata.set(O,targetMetadata);}varmetadataMap=targetMetadata.get(P);if(IsUndefined(metadataMap)){if(!Create)returnundefined;metadataMap=new_Map();targetMetadata.set(P,metadataMap);}returnmetadataMap;}}}reflect-metadata把meta数据存储在全局单例对象中统一管理。reflect-metadata并没有具体实现反射,而是提供了一个工具库来辅助实现反射。6最后,现在让我们看看前面的问题。为什么需要控制反转?什么是依赖注入?装饰师是做什么的?模块(@Module)中provider、import、export的实现原理是什么?1和2我想我之前已经说清楚了。如果还是有点模糊,建议回去再看一遍,参考一些其他的文章和资料,通过不同作者的思考帮助理解知识。6.1问题[34]概述:Nest利用反射技术实现控制反转,提供元编程能力。开发者使用@Module装饰器来装饰类和定义元数据(providers\imports\exports),元数据存储在全局对象中(使用reflect-metadata库)。程序运行后,Nest框架内部的控制程序读取并注册模块树,扫描元数据并将类实例化为provider,根据模块元数据中providers\imports\exports的定义,所有模块provide在提供者中寻找当前类的其他依赖类的实例(提供者),找到后通过构造函数注入。