持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天。点击查看活动详情
开发风格用法
单一规则
- 坚持每个文件只定义一样东西(例如服务或组件)。
- 考虑把文件大小限制在 400 行代码以内。
小函数
- 坚持定义简单函数
- 考虑限制在 75 行之内。
命名
- 小驼峰形式(
camelCase):符号、属性、方法、管道名、非组件指令的选择器、常量。 小驼峰(也叫标准驼峰)形式的第一个字母要使用小写形式。比如 "selectedHero"。 - 大驼峰形式(
UpperCamelCase)或叫帕斯卡形式(PascalCase):类名(包括用来定义组件、接口、NgModule、指令、管道等的类)。 大驼峰形式的第一个字母要使用大写形式。比如 "HeroListComponent"。 - 中线形式(
dash-case)或叫烤串形式(kebab-case):文件名中的描述部分,组件的选择器。比如 "app-hero-list"。 - 下划线形式(
underscore_case)或叫蛇形形式(snake_case):在Angular中没有典型用法。蛇形形式使用下划线连接各个单词。 比如 "convert_link_mode"。 - 大写下划线形式(
UPPER_UNDERSCORE_CASE)或叫大写蛇形形式(UPPER_SNAKE_CASE):传统的常量写法(可以接受,但更推荐用小驼峰形式(camelCase) 大蛇形形式使用下划线分隔的全大写单词。比如 "FIX_ME"。
坚持所有符号使用一致的命名规则。
- 坚持遵循同一个模式来描述符号的特性和类型。推荐的模式为
feature.type.ts。 - 目录名和文件名应该清楚的传递它们的意图。 例如,
app/heroes/hero-list.component.ts包含了一个用来管理英雄列表的组件。
使用点和横杠来分隔文件名
- 坚持使用惯用的后缀来描述类型,包括
*.service、*.component、*.pipe、.module、.directive。 必要时可以创建更多类型名,但必须注意,不要创建太多。 | 符号名 | 文件名 | | --- | --- | |HeroDetailComponent|hero-detail.component.ts| |HeroesComponent|heroes.component.ts| |@Pipe({ name: 'initCaps' })export class InitCapsPipe implements PipeTransform { }|init-caps.pipe.ts| | | |
组件选择器
- 坚持使用中线命名法(
dashed-case)或叫烤串命名法(kebab-case)来命名组件的元素选择器。 - 坚持使用带连字符的小写元素选择器值(例如
admin-users)。 - 坚持为组件选择器添加自定义前缀。 例如,
toh前缀表示Tour of Heroes(英雄指南),而前缀admin表示管理特性区。 - 坚持使用前缀来识别特性区或者应用程序本身。
@Component({
selector: "toh-hero",
})
export class HeroComponent {}
指令选择器
- 坚持使用小驼峰形式命名指令的选择器。
- 为何?可以让指令中的属性名与视图中绑定的属性名保持一致。
@Directive({
selector: "[tohValidate]",
})
export class ValidateDirective {}
管道名 (pipe)
- 坚持为所有管道使用一致的命名约定,用它们的特性来命名。 管道类名应该使用
UpperCamelCase(类名的通用约定),而相应的name字符串应该使用lowerCamelCase。name字符串中不应该使用中线(“中线格式”或“烤串格式”)。 | 符号名 | 文件名 | | --- | --- | |@Pipe({ name: 'ellipsis' }))export class EllipsisPipe implements PipeTransform { }|ellipsis.pipe.ts| |@Pipe({ name: 'initCaps' })export class InitCapsPipe implements PipeTransform { }| init-caps.pipe.ts| | | |
单元测试文件名
- 坚持测试规格文件名与被测试组件文件名相同。
- 坚持测试规格文件名添加
.spec后缀。
组件
heroes.component.spec.ts;
hero - list.component.spec.ts;
hero - detail.component.spec.ts;
服务
logger.service.spec.ts;
hero.service.spec.ts;
filter - text.service.spec.ts;
管道
ellipsis.pipe.spec.ts;
init - caps.pipe.spec.ts;
总体结构的指导原则
- 坚持把所有源代码都放到名为
src的目录里。 - 坚持如果组件具有多个伴生文件 (
.ts、.html、.css 和 .spec),就为它创建一个文件夹。
共享特性模块
- 坚持在
shared目录中创建名叫SharedModule的特性模块(例如在app/shared/shared.module.ts中定义SharedModule)。 - 坚持在共享模块中声明那些可能被特性模块引用的可复用组件、指令和管道。
内联输入和输出属性装饰器
- 坚持 使用
@Input()和@Output(),而非@Directive和@Component装饰器的inputs和outputs属性: - 坚持把
@Input()或者@Output()放到所装饰的属性的同一行。
@Component({
selector: "toh-hero-button",
template: `
<button>{{ label }}</button>
`,
})
export class HeroButtonComponent {
@Output() heroChange = new EventEmitter<any>();
@Input() label: string;
}
成员顺序
- 坚持把属性成员放在前面,方法成员放在后面。
- 坚持先放公共成员,再放私有成员,并按照字母顺序排列。
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);
}
}
把逻辑放到服务里
import { Component, OnInit } from "@angular/core";
import { Hero, HeroService } from "../shared";
@Component({
selector: "toh-hero-list",
template: `
...
`,
})
export class HeroListComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) {}
getHeroes() {
this.heroes = [];
this.heroService.getHeroes().subscribe((heroes) => (this.heroes = heroes));
}
ngOnInit() {
this.getHeroes();
}
}
不要给输出属性加前缀
/* avoid */
@Component({
selector: 'toh-hero',
template: `...`
})
export class HeroComponent {
@Output() onSavedTheDay = new EventEmitter<boolean>();
}
<!-- avoid -->
<toh-hero (onSavedTheDay)="onSavedTheDay($event)"></toh-hero>
<!-- good -->
export class HeroComponent {
@Output() savedTheDay = new EventEmitter<boolean>();
}
<toh-hero (savedTheDay)="onSavedTheDay($event)"></toh-hero>
把表现层逻辑放到组件类里
/* avoid */
@Component({
selector: "toh-hero-list",
template: `
<section>
Our list of heroes:
<hero-profile *ngFor="let hero of heroes" [hero]="hero"> </hero-profile>
Total powers: {{ totalPowers }}
Average power: {{ totalPowers / heroes.length }}
</section>
`,
})
export class HeroListComponent {
heroes: Hero[];
totalPowers: number;
}
<!-- good -->
@Component({
selector: 'toh-hero-list',
template: `
<section>
Our list of heroes:
<toh-hero *ngFor="let hero of heroes" [hero]="hero">
</toh-hero>
Total powers: {{totalPowers}}<br>
Average power: {{avgPower}}
</section>
`
})
export class HeroListComponent {
heroes: Hero[];
totalPowers: number;
get avgPower() {
return this.totalPowers / this.heroes.length;
}
}
提供一个服务
- 坚持在服务的
@Injectable装饰器上指定通过应用的根注入器提供服务。坚持当使用类型作为令牌来注入服务的依赖时,使用@Injectable()类装饰器,而非@Inject()参数装饰器。
/* avoid */
export class HeroArena {
constructor(
@Inject(HeroService) private heroService: HeroService,
@Inject(HttpClient) private http: HttpClient
) {}
}
/* good */
@Injectable()
export class HeroArena {
constructor(private heroService: HeroService, private http: HttpClient) {}
}
管道 (Pipe)
public today=new Date();
<p>{{today | date:'yyyy-MM-dd HH:mm:ss' }}</p>
大小写转换
<p>{{str | uppercase}}</p>
//转换成大写
<p>{{str | lowercase}}</p>
//转换成小写
日期格式转换
<p>{{today | date:'yyyy-MM-dd HH:mm:ss' }}</p>
小数位数
<p>{{p | number:'1.2-4'}}</p>
JavaScript 对象序列化
JavaScript 对象序列化
<p>{{ { name: 'semlinker' } | json }}</p>
<!-- Output: { "name": "semlinker" } -->
slice
<p>{{ 'semlinker' | slice:0:3 | uppercase }}</p>
<!-- Output: SEM -->
自定义管道
WelcomePipe定义
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({ name: "welcome" })
export class WelcomePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== "string") {
throw new Error("Invalid pipe argument for WelcomePipe");
}
return "Welcome to " + value;
}
}
WelcomePipe使用
<div>
<p ngNonBindable>{{ 'semlinker' | welcome }}</p>
<p>{{ 'semlinker' | welcome }}</p>
<!-- Output: Welcome to semlinker -->
</div>
RepeatPipe定义
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({ name: "repeat" })
export class RepeatPipe implements PipeTransform {
transform(value: any, times: number) {
return value.repeat(times);
}
}
RepeatPipe使用
<div>
<p ngNonBindable>{{ 'lo' | repeat:3 }}</p>
<p>{{ 'lo' | repeat:3 }}</p>
<!-- Output: lololo -->
</div>