摘要:组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象备份。
说明:参照了Angular1.x+es2015的中文翻译,并将个人觉得不合适、不正确的地方进行了修改,欢迎批评指正。架构,文件结构,组件,单向数据流以及最佳实践
来自@toddmotto团队的实用编码指南
Angular 的编码风格以及架构已经使用ES2015进行重写,这些在AngularJS 1.5+的变化可以更好帮助您的更好的升级到Angular2.。 这份指南包括了新的单向数据流,事件委托,组件架构和组件路由。
老版本的指南你可以在这里找到,在这里能看到最新的。
加入终极的 AngularJS 学习经验,完全掌握初级和高级的 AngularJS 特性,构建更快,易于扩展的真实应用程序。目录
模块结构
基本概念
根模块
组件模块
公共模块
低级别模块
文件命名规范
可扩展的文件结构
组件
基本概念
支持的属性
控制器
当向数据流和事件
有状态组件
无状态组件
路由组件
指令
基本概念
推荐的属性
常量和类
服务
基本概念
服务的类
样式
TypeScript 和工具
状态管理
资源
文档
贡献
Modular architectureAngular中的每个模块都是一个模块组件。模块组件是包括了组件逻辑,模板,路由和子组件的根。
Module theory模块的设计直接反映到我们的文件夹结构,从而保证我们项目的可维护性和可预测性。 我们最好应该有三个高层次的模块:根模块,组件模块和常用模块。根模块定义用于启动 app 和相应的模板的基础模块。 然后导入我们需要依赖的组件和通用模块。组件和通用模块然后需要低级别的组件模块,包含我们的组件,控制器,服务,指令,过滤器和给可重复使用的功能进行测试。
回到顶部
Root module根模块以一个根组件开始,它定义了整个应用程序的基本元素和路由出口,例如使用ui-router展示ui-view。
// app.component.ts export const AppComponent: angular.IComponentOptions = { template: `Hello world ` };
随着AppComponent导入和使用.component("app", AppComponent)注册,一个根模块就创建了。进一步导入子模块(组件和公共模块)包括与应用程序相关的所有组件。你可能会注意到在这里也导入了样式,我们将在本直男的后面章节介绍这个。
// app.ts import angular from "angular"; import uiRouter from "angular-ui-router"; import { AppComponent } from "./app.component"; import { ComponentsModule } from "./components/components.module"; import { CommonModule } from "./common/common.module"; import "./app.scss"; const root = angular .module("app", [ ComponentsModule, CommonModule, uiRouter ]) .component("app", AppComponent) .name; export default root;
回到顶部
Component module一个组件模块是引用所有可复用组件的容器。在上面我们看到如何导入Components并且将他们注入到根模块,这里给了我们一个导入所有应用程序需要的组件的地方。我们要求这些模块与其他模块都是解耦的,因此可以很容易的移动到其他任何应用程序中。
import angular from "angular"; import { CalendarModule } from "./calendar/calendar.module"; import { EventsModule } from "./events/events.module"; export const ComponentsModule = angular .module("app.components", [ CalendarModule, EventsModule ]) .name;
回到顶部
Common module公共模块是引用所有为应用程序提供的特殊组件的容器,我们不希望它在其他应用程序中使用。这可以是布局,导航和页脚之类的东西。在上面我们看到如何导入Common并且将他们注入到根模块,这里是给了我们一个导入应用程序需要的所有的公共组件的地方。
import angular from "angular"; import { NavModule } from "./nav/nav.module"; import { FooterModule } from "./footer/footer.module"; export const CommonModule = angular .module("app.common", [ NavModule, FooterModule ]) .name;
回到顶部
Low-level modulesAlways remember to add the .name suffix to each export when creating a new module, not when referencing one. You"ll noticed routing definitions also exist here, we"ll come onto this in later chapters in this guide.
低级别的模块是包含每个功能块逻辑的独立组件。每个模块都将定义成一个可以被导入较高级别的多带带模块,例如一个组件或者公共模块,如下所示。 。一定要记住每次创建一个新的模块,而非引用的时候,记得给每个export中添加.name的后缀。你会注意到路由定义也在这里,我们将在随后的部分讲到它。
import angular from "angular"; import uiRouter from "angular-ui-router"; import { CalendarComponent } from "./calendar.component"; import "./calendar.scss"; export const CalendarModule = angular .module("calendar", [ uiRouter ]) .component("calendar", CalendarComponent) .config(($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider) => { $stateProvider .state("calendar", { url: "/calendar", component: "calendar" }); $urlRouterProvider.otherwise("/"); }) .name;
回到顶部
File naming conventions使用小写并保持命名的简洁, 使用组件名称举例, calendar.*.ts*, calendar-grid.*.ts - 将文件类型的名称放到中间。使用 index.ts 作为模块的定义文件,这样就可以通过目录名导入模块了。
index.ts calendar.component.ts calendar.service.ts calendar.directive.ts calendar.filter.ts calendar.spec.ts calendar.html calendar.scss
回到顶部
Scalable file structure文件目录结构非常重要,它有利于我们更好的扩展和预测。下面的例子展示了模块组件的基本架构。
├── app/ │ ├── components/ │ │ ├── calendar/ │ │ │ ├── index.ts │ │ │ ├── calendar.component.ts │ │ │ ├── calendar.service.ts │ │ │ ├── calendar.spec.ts │ │ │ ├── calendar.html │ │ │ ├── calendar.scss │ │ │ └── calendar-grid/ │ │ │ ├── index.ts │ │ │ ├── calendar-grid.component.ts │ │ │ ├── calendar-grid.directive.ts │ │ │ ├── calendar-grid.filter.ts │ │ │ ├── calendar-grid.spec.ts │ │ │ ├── calendar-grid.html │ │ │ └── calendar-grid.scss │ │ ├── events/ │ │ │ ├── index.ts │ │ │ ├── events.component.ts │ │ │ ├── events.directive.ts │ │ │ ├── events.service.ts │ │ │ ├── events.spec.ts │ │ │ ├── events.html │ │ │ ├── events.scss │ │ │ └── events-signup/ │ │ │ ├── index.ts │ │ │ ├── events-signup.controller.ts │ │ │ ├── events-signup.component.ts │ │ │ ├── events-signup.service.ts │ │ │ ├── events-signup.spec.ts │ │ │ ├── events-signup.html │ │ │ └── events-signup.scss │ │ └── components.module.ts │ ├── common/ │ │ ├── nav/ │ │ │ ├── index.ts │ │ │ ├── nav.component.ts │ │ │ ├── nav.service.ts │ │ │ ├── nav.spec.ts │ │ │ ├── nav.html │ │ │ └── nav.scss │ │ ├── footer/ │ │ │ ├── index.ts │ │ │ ├── footer.component.ts │ │ │ ├── footer.service.ts │ │ │ ├── footer.spec.ts │ │ │ ├── footer.html │ │ │ └── footer.scss │ │ └── index.ts │ ├── index.ts │ ├── app.component.ts │ └── app.scss └── index.html
顶级目录仅仅包含了index.html和app/, app/目录中则包含了我们要用到的根模块,组件,公共模块,以及低级别的模块。
回到顶部
Components Component theory组件实际上就是带有控制器的模板。他们即不是指令,也不应该使用组件代替指令,除非你正在用控制器升级“模板指令”,它是最适合作为组件的。 组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象备份。这些都是在AngularJS 1.5及以上推出的新标准。我们创建的所有模板和控制器都可能是一个组件,它可能是是有状态的,无状态或者路由组件。你可以将“组件”看作一段完整的代码,而不仅仅是.component()定义的对象。让我们来探讨一些组件最佳实践和建议,然后你应该可以明白如何通过有状态,无状态和路由组件的概念来组织结构。
回到顶部
Supported properties下面是一些你可能会使用到的.component()属性 :
Property | Support |
---|---|
bindings | Yes, 仅仅使用 "@", "<", "&" |
controller | Yes |
controllerAs | Yes, 默认是$ctrl |
require | Yes (新对象语法) |
template | Yes |
templateUrl | Yes |
transclude | Yes |
回到顶部
Controllers控制器应该仅仅与组件一起使用,而不应该是任何地方。如果你觉得你需要一个控制器,你真正需要的可能是一个来管理特定行的无状态组件。
这里有一些使用Class构建控制器的建议:
始终使用constructor来依赖注入
不要直接导出Class,导出它的名字去允许使用$inject注解
如果你需要访问scope中的语法,请使用箭头函数
另外关于箭头函数,let ctrl = this;也是可以接受的,当然这更取决于使用场景
将所有公开的函数直接绑定到Class
适当的使用生命周期钩子,$onInit, $onChanges, $postLink 和 $onDestroy
注意:$onChanges在$onInit之前被调用,查看这里的扩展阅读对生命周期有进一步的理解
在$onInit使用require去引用其他继承的逻辑
不要使用controllerAs语法去覆盖默认的$ctrl别名,当然也不要再其他地方使用controllerAs
回到顶部
One-way dataflow and Events单向数据流已经在Angular1.5中引入了,并且重新定义了组件之间的通信。
这里有一些使用单向数据流的建议:
在组件中始终使用单向数据绑定语法<来接收数据
不要在任何地方再使用双向绑定语法"="
有 bindings 的组件应该使用 $onChanges 克隆单向绑定数据而阻止通过引用传递对象,并且更新父级数据
在父级方法中使用 $event 作为一个函数参数(查看有状态组件的例子$ctrl.addTodo($event))
从无状态组件传回一个 $event: {} 对象(查看无状态组件的例子this.onAddTodo)
Bonus:使用 .value() 包装 EventEmitter 以便迁移到 Anuglar2,避免手动创一个 $event 对象
为什么?这方便迁移到Angular2,并且在组件内部保持一致性。并且可以让状态可预测。
回到顶部
Stateful components我们来定义下什么叫作“有状态组件”:
本质上通过服务于后端API通信获取状态
不直接改变状态
状态改变的时候渲染子组件
可以作为小的容器组件引用
下面的是一个状态组件案例,它和一个低级别的模块组件共同完成(这只是演示,为了精简省略了一些代码)
/* ----- todo/todo.component.ts ----- */ import { TodoController } from "./todo.controller"; import { TodoService } from "./todo.service"; import { TodoItem } from "../common/model/todo"; export const TodoComponent: angular.IComponentOptions = { controller: TodoController, template: `` }; /* ----- todo/todo.controller.ts ----- */ export class TodoController { static $inject: string[] = ["TodoService"]; todos: TodoItem[]; constructor(private todoService: TodoService) { } $onInit() { this.newTodo = new TodoItem("", false); this.todos = []; this.todoService.getTodos().then(response => this.todos = response); } addTodo({ todo }) { if (!todo) return; this.todos.unshift(todo); this.newTodo = new TodoItem("", false); } } /* ----- todo/index.ts ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .name; /* ----- todo/todo.service.ts ----- */ export class TodoService { static $inject: string[] = ["$http"]; constructor(private $http: angular.IHttpService) { } getTodos() { return this.$http.get("/api/todos").then(response => response.data); } } /* ----- common/model/todo.ts ----- */ export class TodoItem { constructor( public title: string, public completed: boolean) { } ) }
这个例子展示了一个有状态的组件,在控制器通过服务获取状态,然后再将它传递给无状态的子组件。注意这里并没有在模版中使例如如ng-repeat和其他指令。相反,将数据和函数代理到
回到顶部
Stateless components我们来定义下什么叫作“无状态组件”:
使用 bindings: {} 定义输入输出
数据通过属性绑定进入组件(输入)
数据通过事件离开组件(输出)
状态改变,按需传回数据(离去点击和提交事件)
不关心数据来自于哪里,它是无状态的
可高频率复用的组件
也被称作哑巴或者展示性组件
下面是一个无状态组件的例子 (我们使用
/* ----- todo/todo-form/todo-form.component.ts ----- */ import { TodoFormController } from "./todo-form.controller"; export const TodoFormComponent: angular.IComponentOptions = { bindings: { todo: "<", onAddTodo: "&" }, controller: TodoFormController, template: `` }; /* ----- todo/todo-form/todo-form.controller.ts ----- */ import { EventEmitter } from "../common/event-emitter"; import { Event } from "../common/event"; export class TodoFormController { static $inject = ["EventEmitter"]; constructor(private eventEmitter: EventEmitter) {} $onChanges(changes) { if (changes.todo) { this.todo = Object.assign({}, this.todo); } } onSubmit() { if (!this.todo.title) return; // with EventEmitter wrapper this.onAddTodo( eventEmitter({ todo: this.todo }); ); // without EventEmitter wrapper this.onAddTodo(new Event({ todo: this.todo })); } } /* ----- common/event.ts ----- */ export class Event { constructor(public $event: any){ } } /* ----- common/event-emitter.ts ----- */ import { Event } from "./event"; export function EventEmitter(payload: any): Event { return new Event(payload); } /* ----- todo/todo-form/index.ts ----- */ import angular from "angular"; import { EventEmitter } from "./common/event-emitter"; import { TodoFormComponent } from "./todo-form.component"; export const TodoFormModule = angular .module("todo.form", []) .component("todoForm", TodoFormComponent) .value("EventEmitter", EventEmitter) .name;
请注意
回到顶部
Routed components我们来定义下什么叫作“路由组件”:
它本质上是一个具有路由定义的有状态组件
没有 router.ts 文件
我们使用路由组件去定义他们自己的路由逻辑
数据通过路由 resolve “输入” 组件(可选,依然可以在控制器中使用服务调用)
在这个例子中,我们将利用已经存在的
/* ----- todo/todo.component.ts ----- */ import { TodoController } from "./todo.controller"; export const TodoComponent: angular.IComponentOptions = { bindings: { todoData: "<" }, controller: TodoController, template: `` }; /* ----- todo/todo.controller.ts ----- */ import { TodoItem } from "../common/model/todo"; export class TodoController { todos: TodoItem[] = []; $onInit() { this.newTodo = new TodoItem(); } $onChanges(changes) { if (changes.todoData) { this.todos = Object.assign({}, this.todoData); } } addTodo({ todo }) { if (!todo) return; this.todos.unshift(todo); this.newTodo = new TodoItem(); } } /* ----- common/model/todo.ts ----- */ export class TodoItem { constructor( public title: string = "", public completed: boolean = false) { } } /* ----- todo/todo.service.ts ----- */ export class TodoService { static $inject: string[] = ["$http"]; constructor(private $http: angular.IHttpService) { } getTodos() { return this.$http.get("/api/todos").then(response => response.data); } } /* ----- todo/index.ts ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoService } from "./todo.service"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .service("TodoService", TodoService) .config(($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider) => { $stateProvider .state("todos", { url: "/todos", component: "todo", resolve: { todoData: TodoService => TodoService.getTodos(); } }); $urlRouterProvider.otherwise("/"); }) .name;
回到顶部
Directives Directive theory指令给了我们 template ,scope 绑定 ,bindToController,link 和许多其他的事情。使用这些我们应该慎重考虑现在的 .component()。指令不应该再声明模板和控制器了,或者通过绑定接收数据。指令应该仅仅是为了装饰DOM使用。这样,使用 .component() 创建就意味着扩展现有的HTML。简而言之,如果你需要自定义DOM事件/ APIs和逻辑,在组件里使用一个指令将其绑定到模板。如果你需要的足够的数量的 DOM操作,$postLink 生命周期钩子值得考虑,但是这并不是迁移所有的的DOM操作,如果可以的话,你可以使用指令来处理非Angular的事情。
下面是一些使用指令的建议:
不要使用templates、scope,bindToController 或者 controllers
指令通常使用restrict: "A"
在需要的地方使用 compile 和 link
记得在$scope.$on("$destroy", fn);中销毁或者解绑事件处理
回到顶部
Recommended propertiesDue to the fact directives support most of what .component() does (template directives were the original component), I"m recommending limiting your directive Object definitions to only these properties, to avoid using directives incorrectly:
由于指令实际上支持了大多数 .component() 的语法 (模板指令就是最原始的组件), 我建议将指令对象定义限制在这些属性上,去避免错误的使用指令:
Property | Use it? | Why |
---|---|---|
bindToController | No | 在组件中使用 bindings |
compile | Yes | 预编译 DOM 操作/事件 |
controller | No | 使用一个组件 |
controllerAs | No | 使用一个组件 |
link functions | Yes | 对于DOM 操作/事件的前后 |
multiElement | Yes | 文档 |
priority | Yes | 文档 |
require | No | 使用一个组件 |
restrict | Yes | 使用 "A" 去定义一个组件 |
scope | No | 使用一个组件 |
template | No | 使用一个组件 |
templateNamespace | Yes (if you must) | 文档 |
templateUrl | No | 使用一个组件 |
transclude | No | 使用一个组件 |
回到顶部
Constants or Classes这里有使用 TypeScript 和 directives 实现的几种方式,不管是使用箭头函数还是更简单的复制,或者使用 TypeScript 的 Class。选择最适合你或者你团队的,Angular2中使用的是Class。
下面是使用箭头函数表达式使用常量的例子() => ({}),返回一个对象字面量(注意与使用.directive()的不同):
/* ----- todo/todo-autofocus.directive.ts ----- */ import angular from "angular"; export const TodoAutoFocus = ($timeout: angular.ITimeoutService) => ({ restrict: "A", link($scope, $element, $attrs) { $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => { if (!newValue) { return; } $timeout(() => $element[0].focus()); }); } }); TodoAutoFocus.$inject = ["$timeout"]; /* ----- todo/index.ts ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoAutofocus } from "./todo-autofocus.directive"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .directive("todoAutofocus", TodoAutoFocus) .name;
或者使用 TypeScript Class (注意在注册指令的时候手动调用new TodoAutoFocus)去创建一个新对象:
/* ----- todo/todo-autofocus.directive.ts ----- */ import angular from "angular"; export class TodoAutoFocus implements angular.IDirective { static $inject: string[] = ["$timeout"]; restrict: string; constructor(private $timeout: angular.ITimeoutService) { this.restrict = "A"; } link($scope, $element: HTMLElement, $attrs) { $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => { if (!newValue) { return; } $timeout(() => $element[0].focus()); }); } } /* ----- todo/index.ts ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoAutofocus } from "./todo-autofocus.directive"; export const TodoModule = angular .module("todo", []) .component("todo", TodoComponent) .directive("todoAutofocus", ($timeout: angular.ITimeoutService) => new TodoAutoFocus($timeout)) .name;
回到顶部
Services Service theory服务本质上是包含业务逻辑的容器,而我们的组件不应该直接进行请求。服务包含其它内置或外部服务,例如$http,我们可以随时随地的在应用程序注入到组件控制器。我们在开发服务有两种方式,使用.service() 或者 .factory()。使用TypeScript Class,我们应该只使用.service(),通过$inject完成依赖注入。
回到顶部
Classes for Service下面是使用 TypeScript Class 实现
/* ----- todo/todo.service.ts ----- */ export class TodoService { static $inject: string[] = ["$http"]; constructor(private $http: angular.IHttpService) { } getTodos() { return this.$http.get("/api/todos").then(response => response.data); } } /* ----- todo/index.ts ----- */ import angular from "angular"; import { TodoComponent } from "./todo.component"; import { TodoService } from "./todo.service"; export const todo = angular .module("todo", []) .component("todo", TodoComponent) .service("TodoService", TodoService) .name;
回到顶部
Styles利用Webpack 我们现在可以在 *.module.js 中的 .scss文件上使用import 语句,让 Webpack 知道在我们的样式中包含这样的文件。 这样做可以使我们的组件在功能和样式上保持分离,它还与Angular2中使用样式的方式更加贴近。这样做不会让样式像Angular2一样隔离在某个组件上,样式还可以广泛应用到我们的应用程序上,但是它更加易于管理,并且使得我们的应用结构更加易于推理。
If you have some variables or globally used styles like form input elements then these files should still be placed into the root scss folder. e.g. scss/_forms.scss. These global styles can the be @imported into your root module (app.module.js) stylesheet like you would normally do.
如果你有一些变量或者全局使用的样式,像表单的input元素,那么这些文件应该放在根scss文件夹。例如scss/_forms.scss。这些全局的样式可以像通常意义被@imported到根模块(app.module.ts)。
回到顶部
TypeScript and Tooling使用Babel 编译 TypeScript 代码和其他 polyfills
考虑使用 TypeScript让你的代码迁移到Angular2
如果你想支持组件路由,使用ui-routerlatest alpha(查看Readme)
否则你将会被 template: "
考虑使用Webpack来编译你的 TypeScript 代码
使用ngAnnotate 来自动注解 $inject 属性
如何使用ngAnnotate with TypeScript
回到顶部
State management考虑在Angular1.5中使用Redux用于数据管理。
Angular Redux
回到顶部
ResourcesUnderstanding the .component() method
Using "require" with $onInit
Understanding all the lifecycle hooks, $onInit, $onChange, $postLink, $onDestroy
Using "resolve" in routes
Redux and Angular state management
回到顶部
DocumentationFor anything else, including API reference, check the Angular documentation.
ContributingOpen an issue first to discuss potential changes/additions. Please don"t open issues for questions.
License (The MIT License)Copyright (c) 2016 Todd Motto
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
本文github仓库 准备持续翻译一些文章,方便的话给个star!谢谢~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95450.html
摘要:他们即不是指令,也不应该使用组件代替指令,除非你正在用控制器升级模板指令,组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象。 showImg(https://segmentfault.com/img/bVynsJ); 关键词 架构, 文件结构, 组件, 单向数据流以及最佳实践 来自 @toddmotto 团队的编码指南 Angular 的编码...
摘要:技术栈概述大名,顾名思义是在年月正式发布的一套标准。小名,意为第六次变更。本项目,选择的是的推荐配置,唯一注意的是全局变量中把的关键词加上。项目结构公共组件目录,放一些二次封装的等等片段式的。项目的公用样式目录。 技术栈概述 ES2015(ES6) 大名ES2015,顾名思义是 ECMAScript 在2015年6月正式发布的一套标准。小名ES6,意为ECMAScript第六次变更。(...
摘要:三的洋葱模型这里简单讲讲在中是如何分层的,也就是说请求到达服务端后如何层层处理,直到响应请求并将结果返回客户端。从而使得端支持跨域等。 最近已经使用过一段时间的nestjs,让人写着有一种java spring的感觉,nestjs可以使用express的所有中间件,此外完美的支持typescript,与数据库关系映射typeorm配合使用可以快速的编写一个接口网关。本文会介绍一下作...
摘要:三的洋葱模型这里简单讲讲在中是如何分层的,也就是说请求到达服务端后如何层层处理,直到响应请求并将结果返回客户端。从而使得端支持跨域等。 最近已经使用过一段时间的nestjs,让人写着有一种java spring的感觉,nestjs可以使用express的所有中间件,此外完美的支持typescript,与数据库关系映射typeorm配合使用可以快速的编写一个接口网关。本文会介绍一下作...
阅读 3871·2021-09-27 13:35
阅读 1067·2021-09-24 09:48
阅读 2898·2021-09-22 15:42
阅读 2337·2021-09-22 15:28
阅读 3145·2019-08-30 15:43
阅读 2609·2019-08-30 13:52
阅读 2970·2019-08-29 12:48
阅读 1450·2019-08-26 13:55