1. 单一职责
Single Responsibility Principle(SRP)
Apply the SRP to all components , services, and other symbols.
1-1: 定义文件,只实现一个功能, 代码不超400行
- 每个文件只定义一件东西,比如是一个服务或者一个组件
Do define one thing, such as a component, service or per file. - 考虑把文件大小限制在400 行代码以内。
Consider limiting files to 400 lines of code.
1-2: 定义函数,简单,代码不超75行
- 定义简单函数
Do define small functions - 大小限制在75 行以内
Consider limiting to no more than 75 lines
2. 命名
2-1. 文件命名
- 所有符号使用一致的命名规则
Do use consistent names for all symbols - 遵循同一个模式来描述符号的特性和类型。推荐的模式为 feature.type.ts。
- 用点来分隔描述性名字和类型。
在描述性名字中,用横杠来分隔单词。
e.g.
app/heroes/hero-list.component.ts - 使用惯用的后缀来描述类型,包括 .service、.component、*.pipe、.module、.directive。 必要时可以创建更多类型名,但必须注意,不要创建太多。
使用大写驼峰命名法来命名类。符号名匹配它所在的文件名
2-2. 服务名
- 为服务的类名加上 Service 后缀。 例如,获取数据或英雄列表的服务应该命名为 DataService 或 HeroService。
- 有些词汇显然就是服务,比如那些以“-er”后缀结尾的。比如把记日志的服务命名为 Logger 就比 LoggerService 更好些。需要在你的项目中决定这种特例是否可以接受。 但无论如何,都要尽量保持一致。
- 像 Logger 这样的清楚的服务名不需要后缀
- 像 Credit 这样的,服务名是名词,需要一个后缀。
- 当不能明显分辨它是服务还是其它东西时,应该添加后缀。
// hero-data.service.ts
@Injectable()
export class HeroDataService { }
// credit.service.ts
@Injectable()
export class CreditService { }
// logger.service.ts
@Injectable()
export class Logger {}
2-3. 引导 (Bootstrapping)
- 把应用的引导程序和平台相关的逻辑放到名为 main.ts 的文件里
Do putbootstrapping and platform logic
for the app in a file namedmain.ts
- 在引导逻辑中包含错误处理代码。
Do include error handling in the bootstrapping logic - 不要把应用逻辑放在main.ts 中,应该放在组件或服务里。
Avoid putting app logic in main.ts, Instead, consider placing it in a component or service.
// main.ts
import { platformBrowserDynamic } from
'@angular/platform-browser-dynamic'; // 引导启动
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.then(success => console.log(`Bootstrap success`))
.catch(err => console.error(err));
2-4. 组件选择器命名
- 坚持使用中线命名法(dashed-case)或叫烤串命名法(kebab-case)来命名组件的元素选择器
- 坚持使用带连字符的小写元素选择器值
- 坚持为组件选择器添加自定义前缀
e.g.
√ selector: 'toh-hero',
× selector: 'tohHero'
× selector: 'hero'
@Component({
selector: 'toh-hero',
templateUrl: './hero.component.html' })
export class HeroComponent {}
5. 指令选择器( Directive selectors)
)命名
- 坚持为指令的选择器添加自定义前缀.
- 用小驼峰形式拼写非元素选择器,除非该选择器用于匹配原生 HTML 属性。
@Directive({
× selector: '[validate]'
√ selector: '[tohValidate]'
})
export class ValidateDirective {}
2-6. 管道命名
- 用它们的特性来命名
// ellipsis.pipe.ts
@Pipe({ name: 'ellipsis' })
export class EllipsisPipe implements PipeTransform { }
// init-caps.pipe.ts
@Pipe({name: 'initCaps'})
export class InitCapsPipe implements PipeTransform {}
2-7. LIFT 命名
组织app 结构的原则:
- 快速定位
- 一眼识别代码
- 尽量保持扁平结构
- 尽量不重复
Do structure the app such that you can Locate code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to be DRY(Do not Repeat Yourself) .
当你有一组小型、紧密相关的特性时,违反一物一文件的规则可能会更好, 这种情况下单一文件可能会比多个文件更容易发现和理解。注意这个例外。
考虑当同一目录下达到 7 个或更多个文件时应该创建子目录。扁平结构有利于搜索。
某些边界清晰的应用特性或工作流可以做成惰性加载或按需加载的,而不用总是随着应用启动。
坚持把惰性加载特性下的内容放进惰性加载目录中。 典型的惰性加载目录包含路由组件及其子组件以及与它们有关的那些资产和模块。
永远不要直接导入惰性加载的目录。
为何?直接导入并使用此模块会立即加载它,而原本的设计意图是按需加载它。
3. 组件
- 把组件当作元素
- 把模板和样式提取到它们自己的文件
- 当超过 3 行时,把模板和样式提取到一个单独的文件。
- 指定组件相关(component-ralative)的 URL ,给它加上 ./ 前缀
e.g.1. templateUrl: './heroes.component.html',
e.g.2. styleUrls: ['./heroes.component.css'] - 使用内联输入输出类装饰器代替指令和组件的输入输出属性
@Component({
selector: 'toh-hero-button',
template: ` <button>{{label}}</button>`
/* 尽量避免使用组件的输入输出属性
inputs: [
'label'
],
outputs: [
'change'
],
*/
})
export class HeroButtonComponent {
@Output() change = new EventEmitter<any>();
@Input() label: string;
}
- 成员顺序
- 坚持把属性成员放在前面,方法成员放在后面。
Do place properties up top followed by methods. - 坚持先放公共成员,再放私有成员,并按照字母顺序排列。
Do place private members after public members, alphabetized.
properties first, then methods,
public first, then private members
- 把逻辑放到服务里
- 组件中只包含与视图相关的逻辑。所有其他逻辑都放到服务中。
- 把可重用的逻辑放到服务中。
- 不要给输出属性加前缀
- 命名事件时,不要带前缀on
Do name events without the prefix on. - 命名事件处理方法时 , on 前缀后面紧跟着事件名。如果事件的名字本身就带有on, 那么绑定的表达式可能是on-onEvent.
Do name event handler methods with the prefix on followed by the event name.
- 把表现成逻辑放到组件类里
Put presentation logic in the component class
把组件的表现层逻辑放在组件类而非模板里,可以增强测试性、维护性和重复使用性。
@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}}<br>
/* avoid
Average power: {{totalPowers / heroes.length}}
*/
// recommend
Average power: {{avgPower}}
</section>
`
})
export class HeroListComponent {
heroes: Hero[];
totalPowers: number;
// recommend
get avgPower(){
return this.totalPowers / this.heroes.length;
}
}
4. 指令(还没有看,这一部分规则看不明白,等看完再补全)
- 当你需要有表现层逻辑,但没有模板时,使用属性型指令。
Do use attribute directives when you have presentation logic without a template
5. 服务
5-1. 服务总是单例的
- 坚持在同一个注入器内,把服务当做单例使用。用它们来共享数据和功能。
Do use services as singletons within the same injector . Use them for sharing data and functionality.
export class HeroService {
constructor(private http: HttpClient) {}
getHeroes(){
return this.http.get<Hero[]>('/api/heroes')
}
}
5-2 单一职责
- 坚持创建封装在上下文中的单一职责的服务。
Do create services with a single responsibility that is encapsulated by its context. - 坚持当服务成长到超出单一用途时,创建一个新服务。
5-3 提供一个服务
-
坚持在服务的@Injectable 装饰器上指定通过app的 root injector (根注入器)提供服务。
Do provide a service with the app root injector in the @Injectable decorator of the service. -
angular injector 注入器是层次化的,当你在根注入器上提供该服务时,该服务实例在每个需要该服务的类中是共享的。当服务要共享方法或状态时,这是最理想的选择。
-
当你在服务的
@Injectable
中注册服务时,Angular CLI 生产环境构建时使用的优化工具可以进行摇树优化,从而移除那些你的应用中从未用过的服务。
When you register a service in the @Injectable decorator of the service, optimization tools such as those used by the Angular CLI's production builds can perform tree shaking and remove services that aren't used by your app. -
当不同的两个组件需要一个服务的不同的实例时,上面的方法这就不理想了。在这种情况下,对于需要崭新和单独服务实例的组件,最好在组件级提供服务。
// src/app/trreshaking/service.ts
@Injectable({
provideIn: 'root',
})
export class Service {
}
- 使用 @Injectable() 类装饰器
6. 数据服务
- 坚持把数据操作和与数据交互的逻辑重构到服务里
- 坚持让数据服务来负责XHR 调用、本地存储、内存存储或其他数据操作。
why? - 组件的职责是为视图展示或收集信息。它不应该关心如何获取数据,只需要知道向谁请求数据。把如何获取数据的逻辑移动到数据服务里。
7. 生命周期钩子
生命周期钩子使用生命周期钩子来介入到 Angular 暴露的重要事件里。
Use Lifecycle hooks to tap into important events exposed by Angular.
- 坚持实现生命周期钩子接口。
Do implement the lifecycle hook interfaces. - 为何?如果使用强类型的方法签名,编译器和编辑器可以帮你揪出拼写错误。
Why? Lifecycle interfaces prescribe typed method signatures. Use those signatures to flag spelling and syntax mistakes.
网友评论