风格指南:style guide
对所有的组件、服务等等应用单一职责原则 (SRP)。这样可以让应用更干净、更易读、更易维护、更易测试。
1.单组件文件非常容易阅读、维护
,并能防止在版本控制系统里与团队冲突。
2.单组件文件可以防止一些隐蔽的程序缺陷
,当把多个组件合写在同一个文件中时,可能造成共享变量、创建意外的闭包,或者与依赖之间产生意外耦合
等情况。
3.单独的组件通常是该文件默认的导出,可以用路由器实现按需加载
。
4.最关键的是,可以让代码更加可复用
、更容易阅读,减少出错的可能性。
1.简单函数更易于阅读和维护
。
2.小函数可避免易在大函数中产生的隐蔽性错误
,例如与外界共享变量、创建意外的闭包或与依赖之间产生意外耦合等。
3.单函数更易于测试,特别是当它们只做一件事,只为一个目的服务时。
4.简单函数促进代码重用
。
1.语义化
2.文件命名:推荐的模式为 feature.type.html|ts|scss。用点来分割
。
feature:功能描述。用横杠来分割单词
。
type:service、component、pipe、module、directive。
1.坚持使用中线命名法(dashed-case)或叫烤串命名法(kebab-case)来命名组件的元素选择器。
selector: 'toh-hero-button',
2.为组件添加自定义前缀
1.坚持使用小驼峰形式命名指令的选择器。
2.为指令添加自定义前缀
selector: '[tohValidate]'
用cli默认生成的格式!eg:
坚持为 RoutingModule 类名添加 RoutingModule 后缀。
坚持为 RoutingModule 的文件名添加 -routing.module.ts 后缀。
1.坚持使用小写驼峰命名法来命名属性和方法。
2.避免为私有属性和方法添加下划线前缀。
eg:避免:
private _toastCount: number;
1.坚持在第三方导入和应用导入之间留一个空行。eg:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ExceptionService, SpinnerService, ToastService } from '../../core';
import { Hero } from './hero.model';
坚持组织应用的结构,力求:快速定位 (Locate) 代码、一眼识别 (Identify) 代码、 尽量保持扁平结构 (Flattest) 和尝试 (Try) 遵循 DRY (Do Not Repeat Yourself, 不重复自己) 原则。
快速定位:坚持为每个特性区
创建一个 NgModule(特性模块
)。
一眼识别:语义化
扁平:考虑当同一目录下达到 7 个或更多个文件时创建子目录。
1.坚持在 shared 目录中创建名叫 SharedModule 的特性模块(例如在 app/shared/shared.module.ts 中定义 SharedModule)。
2.坚持在共享模块中声明那些可能被特性模块引用的可复用
组件、指令和管道。
3.考虑
不要在共享模块中提供服务。服务通常是单例的,应该在整个应用或一个特定的特性模块中只有一份。
避免在 SharedModule 中指定应用级
的单例服务提供商。如果是刻意要得到多个服务单例也行,不过还是要小心。
4.坚持在 SharedModule 中导入所有模块都需要的资产(例如 CommonModule 和 FormsModule)。
5.坚持在 SharedModule 中声明所有组件、指令和管道。
坚持从 SharedModule 中导出其它特性模块所需的全部符号。
为何? SharedModule 的存在,能让常用的组件、指令和管道在很多其它模块的组件模板中都自动可用。
1.坚持在 core 目录下创建一个名叫 CoreModule 的特性模块(例如在 app/core/core.module.ts 中定义 CoreModule)。
2.考虑把那些数量庞大、辅助性的、只用一次
的类收集到核心模块中,让特性模块的结构更清晰简明。
坚持把那些“只用一次
”的类收集到 CoreModule 中,并对外隐藏它们的实现细节。简化的 AppModule 会导入 CoreModule,并且把它作为整个应用的总指挥。
坚持把应用级、只用一次的组件收集到 CoreModule 中。 只在应用启动时从 AppModule 中导入它一次,以后再也不要导入它(例如 NavComponent 和 SpinnerComponent)。
为何?真实世界中的应用会有很多只用一次的组件(例如加载动画、消息浮层、模态框等),它们只会在 AppComponent 的模板中出现。 不会在其它地方导入它们,所以没有共享的价值。 然而它们又太大了,放在根目录中就会显得乱七八糟的。
3.坚持把要共享给整个应用的单例服务放进 CoreModule 中(例如 ExceptionService 和 LoggerService)。
4.坚持导入 CoreModule 中的资产所需要的全部模块(例如 CommonModule 和 FormsModule)。
5.避免在 AppModule 之外的任何地方导入 CoreModule。
1.应该只有 AppModule 才允许导入 CoreModule。
2.坚持防范多次导入 CoreModule,并通过添加守卫逻辑来尽快失败。
为何?守卫可以阻止对 CoreModule 的多次导入。
为何?守卫会禁止创建单例服务的多个实例。
考虑给组件一个元素选择器,而不是属性或类选择器。
为何?查看组件模板的 HTML 时,更容易识别一个符号是组件还是指令。
1.坚持当超过 3 行时,把模板和样式提取到一个单独的文件。
2.坚持指定相对于模块的 URL ,给它加上 ./ 前缀。
1.坚持 使用 @Input() 和 @Output(),而非 @Directive 和 @Component 装饰器的 inputs 和 outputs 属性:
2.坚持把 @Input() 或者 @Output() 放到所装饰的属性的同一行。
1.避免除非有重要目的,否则不要为输入和输出指定别名。eg:
避免:
@Output('changeEvent') change = new EventEmitter();
@Input('labelAttribute') label: string;
1.坚持把属性成员放在前面,方法成员放在后面。
2.坚持先放公共成员,再放私有成员,并按照字母顺序排列。
eg:
// public properties
message: string;
title: string;
// private fields
private defaults = {
title: '',
message: 'May the Force be with you'
};
// public methods
activate(message = this.defaults.message, title = this.defaults.title) {
this.title = title;
this.message = message;
this.show();
}
// private methods
private hide() {
this.toastElement.style.opacity = 0;
window.setTimeout(() => this.toastElement.style.zIndex = 0, 400);
}
坚持在组件中只包含与视图相关的逻辑。所有其它逻辑都应该放到服务中。
坚持把可重用的逻辑放到服务中,保持组件简单,聚焦于它们预期目的。
eg:
ajax的一些处理逻辑放出去,我只是调用接口就ok了!
坚持命名事件时,不要带前缀 on。
坚持把事件处理器方法命名为 on 前缀之后紧跟着事件名。
eg:
坚持把表现层逻辑放进组件类中,而不要放在模板里。
为何?逻辑应该只出现在一个地方(组件类里)而不应分散在两个地方。
为何?将组件的表现层逻辑放到组件类而非模板里,可以增强测试性、维护性和重复使用性。
eg:
error:
Average power: {{totalPowers / heroes.length}}
ok:
Average power: {{avgPower}}
// ts:
get avgPower() {
return this.totalPowers / this.heroes.length;
}
坚持当你需要有表现层逻辑,但没有模板时,使用属性型指令。
为何?属性型指令没有模板。
为何?一个元素可以使用多个属性型指令。
@Directive({
selector: '[tohHighlight]'
})
export class HighlightDirective {
@HostListener('mouseover') onMouseEnter() {
// do highlight work
}
}
// html:
Bombasta
HostListener 和 HostBinding 装饰器 vs. 组件元数据 host
考虑优先使用 @HostListener 和 @HostBinding,而不是 @Directive 和 @Component 装饰器的 host 属性。
坚持让你的选择保持一致。
import { Directive, HostBinding, HostListener } from '@angular/core';
@Directive({
selector: '[tohValidator]'
})
export class ValidatorDirective {
@HostBinding('attr.role') role = 'button';
@HostListener('mouseenter') onMouseEnter() {
// do work
}
}