Fork me on GitHub
tc9011's

Angular官方文档学习笔记之风格指南

2017020411358aj1.jpeg

单一职责

单一法则

坚持每个文件只定义一样东西(例如服务或组件)。

考虑把文件大小限制在 400 行代码以内。

简单函数

坚持定义简单函数

考虑限制在 75 行之内。

命名

总体命名指导原则

坚持所有符号使用一致的命名规则。

坚持遵循同一个模式来描述符号的特性和类型。推荐的模式为feature.type.ts

使用点和横杠分隔文件名

坚持 在描述性名字中,用横杠来分隔单词。

坚持使用点来分隔描述性名字和类型。

坚持遵循先描述组件特性,再描述它的类型的模式,对所有组件使用一致的类型命名规则。推荐的模式为feature.type.ts

坚持使用惯用的后缀来描述类型,包括*.service*.component*.pipe.module.directive。 必要时可以创建更多类型名,但必须注意,不要创建太多。

符号名与文件名

坚持为所有东西使用一致的命名约定,以它们所代表的东西命名。

坚持使用大写驼峰命名法来命名类。符号名匹配它所在的文件名。

坚持在符号名后面追加约定的类型后缀(例如ComponentDirectiveModulePipeService)。

坚持在文件名后面追加约定的类型后缀(例如.component.ts.directive.ts.module.ts.pipe.ts.service.ts)。

服务名

坚持使用一致的规则命名服务,以它们的特性来命名。

坚持使用大写驼峰命名法命名服务。

坚持添加Service后缀,当不清楚它们是什么时(例如当它们是名词时)。

引导

坚持把应用的引导程序和平台相关的逻辑放到名为main.ts的文件里。

坚持在引导逻辑中包含错误处理代码。

避免把应用逻辑放在main.ts中,而应放在组件或服务里。

指令选择器

坚持使用小驼峰命名法来命名指令的选择器。

为组件添加自定义前缀

坚持使用带连字符的小写元素选择器值(例如admin-users)。

坚持为组件选择器添加自定义前缀。 例如,toh前缀表示 Tour of Heroes(英雄指南),而前缀`admin表示管理特性区。

坚持使用前缀来识别特性区或者应用程序本身。

为指令添加自定义前缀

坚持为指令的选择器添加自定义前缀(例如前缀toh来自Tour of Heroes)。

坚持小驼峰形式拼写非元素选择器,除非该选择器用于匹配原生 HTML 属性。

管道名

坚持为所有管道使用一致的命名约定,用它们的特性来命名。

单元测试文件名

坚持测试规格文件名与被测试组件文件名相同。

坚持测试规格文件名添加.spec后缀。

2017020481957FireShot Capture 10 - 风格指南 - ts - GUIDE_ - https___angular.cn_docs_ts_latest_.png

端到端测试文件名

坚持端到端测试规格文件和它们所测试的特性同名,添加.e2e-spec后缀。

Angular NgModule 命名

坚持为符号名添加Module后缀

坚持为文件名添加.module.ts扩展名。

坚持用特性名和所在目录命名模块。

坚持RoutingModule 类名添加RoutingModule后缀。

坚持RoutingModule 的文件名添加-routing.module.ts后缀。

代码约定

坚持使用大写驼峰命名法来命名类。

常量

坚持const声明变量,除非它们的值在应用的生命周期内会发生变化。

考虑 把常量名拼写为小驼峰格式

坚持容许现存的const常量沿用大写蛇形命名法

1
2
3
export const mockHeroes = ['Sam', 'Jill']; // prefer
export const heroesUrl = 'api/heroes'; // prefer
export const VILLAINS_URL = 'api/villains'; // tolerate

接口

坚持使用大写驼峰命名法来命名接口。

考虑不要在接口名字前面加I前缀。(TypeScript 指导原则不建议使用 “I” 前缀。)

考虑用类代替接口。(单独一个类的代码量小于类+接口;类可以作为接口使用,只是用implements代替extends而已;在 Angular 依赖注入系统中,接口类可以作为服务提供商的查找令牌。)

属性和方法

坚持使用小写驼峰命名法来命名属性和方法。

避免为私有属性和方法添加下划线前缀。

导入语句中的空行

坚持在第三方导入和应用导入之间留一个空行。

考虑按模块名字的字母顺排列导入行。

考虑在解构表达式中按字母顺序排列导入的东西。

应用结构与Angular模块

所有应用程序的源代码都放到名叫app的目录里。 所有特性区都在自己的文件夹中,带有它们自己的 Angular 模块。

所有内容都遵循每个文件一个特性的原则。每个组件、服务和管道都在自己的文件里。 所有第三方程序包保存到其它目录里,不是app目录。

LIFT

坚持组织应用的结构,达到这些目的:快速定位 (Locate) 代码、一眼识别 (Identify) 代码、 尽量保持扁平结构 (Flattest) 和尝试 (Try) 遵循DRY (Do Not Repeat Yourself, 不重复自己) 原则。

坚持四项基本原则定义文件结构,上面的原则是按重要顺序排列的。

定位

坚持直观、简单和快速地定位代码。

识别

坚持命名文件到这个程度:看到名字立刻知道它包含了什么,代表了什么。

坚持文件名要具有说明性,确保文件中只包含一个组件。

避免创建包含多个组件、服务或者混合体的文件。

扁平

坚持尽可能保持扁平的目录结构。

考虑当同一目录下达到 7 个或更多个文件时创建子目录。

考虑配置 IDE,以隐藏无关的文件,例如生成出来的.js文件和.js.map文件等。

T-DRY (尝试不重复自己)

坚持 DRY(Don’t Repeat Yourself,不重复自己)。

避免过度 DRY,以致牺牲了阅读性。

总体结构指导原则

坚持从零开始,但要考虑应用程序接下来的路往哪儿走。

坚持有一个近期实施方案和一个长期的愿景。

坚持把所有源代码都放到名为app的目录里。

坚持如果组件具有多个伴隨文件 (.ts.html.css.spec),就为它创建一个文件夹。

下面是符合规范的目录和文件结构:

201702047903FireShot Capture 11 - 风格指南 - ts - GUIDE_ - https___angular.cn_docs_ts_latest_.png

按特性组织的目录结构

坚持根据特性区命名目录。

坚持为每个特性区创建一个 Angular 模块。

应用的根模块

坚持在应用的根目录创建一个 Angular 模块(例如/app)。

考虑把根模块命名为app.module.ts

特性模块

坚持为应用中每个明显的特性创建一个 Angular 模块。

坚持把特性模块放在与特性区同名的目录中(例如app/heroes)。

坚持特性模块的文件名应该能反映出特性区的名字和目录(例如app/heroes/heroes.module.ts)。

坚持特性模块的符号名应该能反映出特性区、目录和文件名(例如在app/heroes/heroes.module.ts中定义HeroesModule)。

共享特性模块

坚持shared目录中创建名叫SharedModule的特性模块(例如在app/shared/shared.module.ts中定义SharedModule)。

坚持把可能被应用其它特性模块使用的公共组件、指令和管道放在SharedModule中,这些资产倾向于共享自己的新实例(而不是单例)。

坚持SharedModule中导入所有模块都需要的资产(例如CommonModuleFormsModule)。

坚持SharedModule中声明所有组件、指令和管道。

坚持SharedModule中导出其它特性模块所需的全部符号。

避免SharedModule中指定应用级的单例服务提供商。但如果是故意设计的单例也可以,不过还是要小心。

核心特性模块

坚持把那些“只用一次”的类收集到CoreModule中,并对外隐藏它们的实现细节。简化的AppModule会导入CoreModule,并且把它作为整个应用的总指挥。

坚持core目录下创建一个名叫CoreModule的特性模块(例如在app/core/core.module.ts中定义CoreModule)。

坚持把一个要共享给整个应用的单例服务放进CoreModule中(例如ExceptionServiceLoggerService)。

坚持导入CoreModule中的资产所需要的全部模块(例如CommonModuleFormsModule)。

坚持把应用级、只用一次的组件收集到CoreModule中。 只在应用启动时从AppModule中导入它一次,以后再也不要导入它(例如NavComponentSpinnerComponent)。

避免AppModule之外的任何地方导入CoreModule

坚持CoreModule中导出AppModule需导入的所有符号,使它们在所有特性模块中可用。

防止多次导入CoreModule

坚持防范多次导入CoreModule,并通过添加守卫逻辑来尽快失败。(守卫可以阻止对CoreModule的多次导入,守卫会禁止创建单例服务的多个实例。)

1
2
3
4
5
6
7
app/core/module-import-guard.ts
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
if (parentModule) {
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
}
}

惰性加载的目录

坚持把惰性加载特性下的内容放进惰性加载目录中。 典型的惰性加载目录包含路由组件及其子组件以及与它们有关的那些资产和模块。

永远不要直接导入惰性加载的目录

避免让兄弟模块和父模块直接导入惰性加载特性中的模块。

组件

组件选择器命名

坚持使用中线 (dashed) 命名法烤串 (kebab) 命名法来命名组件中的元素选择器。

把组件当做元素

坚持通过选择器把组件定义为元素。

把模板和样式提取到它们自己的文件

坚持当超过 3 行时,把模板和样式提取到一个单独的文件。

坚持把模板文件命名为[component-name].component.html,其中,[component-name] 是组件名。

坚持把样式文件命名为[component-name].component.css,其中,[component-name] 是组件名。

内联输入和输出属性装饰器

坚持 使用@Input@Output,而非@Directive@Component装饰器的inputsoutputs属性:

坚持@Input()或者@Output()放到所装饰的属性的同一行。

避免重命名输入和输出

避免重命名输入和输出。

成员顺序

坚持把属性成员放在前面,方法成员放在后面。

坚持先放公共成员,再放私有成员,并按照字母顺序排列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export class ToastComponent implements OnInit {
// public properties
message: string;
title: string;
// private fields
private defaults = {
title: '',
message: 'May the Force be with You'
};
private toastElement: any;
// public methods
activate(message = this.defaults.message, title = this.defaults.title) {
this.title = title;
this.message = message;
this.show();
}
ngOnInit() {
this.toastElement = document.getElementById('toh-toast');
}
// private methods
private hide() {
this.toastElement.style.opacity = 0;
window.setTimeout(() => this.toastElement.style.zIndex = 0, 400);
}
private show() {
console.log(this.message);
this.toastElement.style.opacity = 1;
this.toastElement.style.zIndex = 9999;
window.setTimeout(() => this.hide(), 2500);
}
}

把逻辑放到服务里

坚持在组件中只包含与视图相关的逻辑。所有其它逻辑都应该放到服务中。

坚持把可重用的逻辑放到服务中,保持组件简单,聚焦于它们预期目的。

不要给输出属性加前缀

坚持命名事件时,不要带前缀on

坚持把事件处理器方法命名为on前缀之后紧跟着事件名。

1
2
3
4
5
export class HeroComponent {
@Output() savedTheDay = new EventEmitter<boolean>();
}
<toh-hero (savedTheDay)="onSavedTheDay($event)"></toh-hero>

把表现层逻辑放到组件类里

坚持把表现层逻辑放进组件类中,而不要放在模板里。

指令

使用指令来加强已有元素

坚持当你需要有表现层逻辑,但没有模板时,使用属性型指令。

使用 HostListener 和 HostBinding 类装饰器

考虑优先使用@HostListener@HostBinding,而不是@Directive@Component装饰器的host属性。

坚持让你的选择保持一致。

1
2
3
4
5
6
7
8
9
10
import { Directive, HostBinding, HostListener } from '@angular/core';
@Directive({
selector: '[tohValidator]'
})
export class ValidatorDirective {
@HostBinding('attr.role') role = 'button';
@HostListener('mouseenter') onMouseEnter() {
// do work
}
}

服务

注入器中,服务总是单例的

坚持在同一个注入器内,把服务当做单例使用。用它们来共享数据和功能。

单一职责

坚持创建单一职责的服务,用职责封装在它的上下文中。

坚持当服务成长到超出单一用途时,创建一个新服务。

提供一个服务

坚持将服务提供到共享范围内的顶级组件的 Angular 注入器。

使用 @Injectable() 类装饰器

坚持当使用类型作为令牌来注入服务的依赖时,使用@Injectable类装饰器,而非@Inject参数装饰器。

数据服务

分离数据调用

坚持把数据操作和互动重构到服务里。

坚持让数据服务来负责 XHR 调用、本地储存、内存储存或者其它数据操作。

生命周期钩子

使用生命周期钩子来介入到 Angular 暴露的重要事件里。

实现生命周期钩子接口

坚持实现生命周期钩子接口。

附录

Codelyzer

坚持使用 codelyzer 来实施本指南。

考虑调整 codelyzer 的规则来满足你的需求。

文档模板和代码片段

坚持使用文件模板或代码片段来帮助实现一致的风格和模式。

考虑使用 Visual Studio Code代码片段 来实施本风格指南。

考虑使用 Atom代码片断来实施本风格指南。

考虑使用 Sublime Text代码片断 来实施本风格指南。

考虑使用 Vim代码片断来实施本风格指南。

汤诚 wechat
欢迎订阅我的微信公众号
坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

tc9011's Notes by Cheng Tang is licensed under a Creative Commons BY-NC-ND 4.0 International License.
汤诚创作并维护的tc9011's Notes博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于tc9011's Notes 博客( http://tc9011.com ),版权所有,侵权必究。