angular风格指南

此文章内容包括官网中的风格指南,及知乎野草文章中关于Angular: Best Practices的总结。

学习angular代码风格及最佳实践能写出更漂亮的程序。

1 单一职责

对所有的组件、服务等等应用单一职责原则 (SRP)。这样可以让应用更干净、更易读、更易维护、更易测试

1.1 单一规则

  • 坚持每个文件只定义一样东西(例如服务或组件)
  • 考虑把文件大小限制在 400 行代码以内。

1.2 小函数

  • 坚持定义简单函数
  • 考虑限制在 75 行之内。

2 命名

2.1 总体命名原则

  • 坚持所有符号使用一致的命名规则。
  • 坚持遵循同一个模式来描述符号的特性和类型。推荐的模式为 feature.type.ts。

例如,app/heroes/hero-list.component.ts 包含了一个用来管理英雄列表的组件

2.2 使用点和横杠来分隔文件名

  • 坚持 在描述性名字中,用横杠来分隔单词。
  • 坚持使用点来分隔描述性名字和类型。
  • 坚持遵循先描述组件特性,再描述它的类型的模式,对所有组件使用一致的类型命名规则。推荐的模式为 feature.type.ts。
  • 坚持使用惯用的后缀来描述类型,包括 *.service、*.component、*.pipe、.module、.directive。 必要时可以创建更多类型名,但必须注意,不要创建太多。

    像 .service 这样的没有简写过的类型名字,描述清楚,毫不含糊。 像 .srv, .svc, 和 .serv 这样的简写可能令人困惑。

2.3 符号名与文件名

  • 坚持为所有东西使用一致的命名约定,以它们所代表的东西命名。
  • 坚持使用大写驼峰命名法来命名类。符号名匹配它所在的文件名。
  • 坚持在符号名后面追加约定的类型后缀(例如 Component、Directive、Module、Pipe、Service)。
  • 坚持在文件名后面追加约定的类型后缀(例如 .component.ts、.directive.ts、.module.ts、.pipe.ts、.service.ts)。

    如符号名:export class HeroesComponent{}

    文件名:heroes.component.ts

2.4 引导

  • 坚持把应用的引导程序和平台相关的逻辑放到名为 main.ts 的文件里。
  • 坚持在引导逻辑中包含错误处理代码。
  • 避免把应用逻辑放在 main.ts 中,而应放在组件或服务里。
    1
    2
    3
    4
    5
    6
    7
    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.5 组件选择器

  • 坚持使用中线命名法(dashed-case)或叫烤串命名法(kebab-case)来命名组件的元素选择器。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    @Component({
    selector: 'tohHeroButton',/****** avoid ******/
    templateUrl: './hero-button.component.html'
    })
    export class HeroButtonComponent {}

    @Component({
    selector: 'toh-hero-button',/****** right ******/
    templateUrl: './hero-button.component.html'
    })
    export class HeroButtonComponent {}

2.6 为组件添加自定义前缀

  • 坚持使用带连字符的小写元素选择器值(例如 admin-users)。
  • 坚持为组件选择器添加自定义前缀。 例如,toh 前缀表示 Tour of Heroes(英雄指南),而前缀 `admin 表示管理特性区。
  • 坚持使用前缀来识别特性区或者应用程序本身。
    1. 防止与其它应用中的组件和原生 HTML 元素发生命名冲突。
    2. 更容易在其它应用中推广和共享组件。
    3. 组件在 DOM 中更容易被区分出来

错误示例:

1
2
3
4
5
6
/* avoid */
// HeroComponent is in the Tour of Heroes feature
@Component({
selector: 'hero'
})
export class HeroComponent {}
1
2
3
4
5
6
/* avoid */
// UsersComponent is in an Admin feature
@Component({
selector: 'users'
})
export class UsersComponent {}

正确示例:

1
2
3
4
@Component({
selector: 'toh-hero'
})
export class HeroComponent {}
1
2
3
4
@Component({
selector: 'admin-users'
})
export class UsersComponent {}

2.7 指令选择器

  • 坚持使用小驼峰形式命名指令的选择器。

2.8 为指令添加自定义前缀

  • 坚持为指令的选择器添加自定义前缀(例如前缀 toh 来自 Tour of Heroes)。
  • 坚持用小驼峰形式拼写非元素选择器,除非该选择器用于匹配原生 HTML 属性。

错误示例:

1
2
3
4
5
/* avoid */
@Directive({
selector: '[validate]'
})
export class ValidateDirective {}

正确示例:

1
2
3
4
@Directive({
selector: '[tohValidate]'
})
export class ValidateDirective {}

2.9 管道名

  • 坚持为所有管道使用一致的命名约定,用它们的特性来命名。
    1
    2
    3
    4
    5
    6
    // 文件名:
    elipsis.pipe.ts

    // 符号名:
    @Pipe({ name: 'ellipsis' })
    export class EllipsisPipe implements PipeTransform { }
1
2
3
4
5
6
// 文件名:
init-caps.pipe.ts

// 符号名:
@Pipe({ name: 'initCaps' })
export class InitCapsPipe implements PipeTransform { }

3 编程约定

3.1 常量

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

4 应用程序结构与NgModule

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

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

4.1 LIFT

  • 坚持组织应用的结构,力求:
    • (Locate)快速定位 代码
    • (Identify)一眼识别 代码
    • (Flattest)尽量保持扁平结构
    • (Try)尝试遵循 DRY (Do Not Repeat Yourself, 不重复自己) 原则
  • 坚持四项基本原则定义文件结构,上面的原则是按重要顺序排列的。

检查应用结构是否合理的方法是问问自己:我能快速打开与此特性有关的所有文件并开始工作吗?

4.2 定位

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

要想高效的工作,就必须能迅速找到文件,特别是当不知道(或不记得)文件名时。 把相关的文件一起放在一个直观的位置可以节省时间。 富有描述性的目录结构会让你和后面的维护者眼前一亮。

4.3 识别

  • 坚持命名文件到这个程度:看到名字立刻知道它包含了什么,代表了什么。
  • 坚持文件名要具有说明性,确保文件中只包含一个组件。
  • 避免创建包含多个组件、服务或者混合体的文件

    当你有一组小型、紧密相关的特性时,违反一物一文件的规则可能会更好, 这种情况下单一文件可能会比多个文件更容易发现和理解。

4.4 扁平

  • 坚持尽可能保持扁平的目录结构。
  • 考虑当同一目录下达到 7 个或更多个文件时创建子目录。
  • 考虑配置 IDE,以隐藏无关的文件,例如生成出来的 .js 文件和 .js.map 文件等。

4.5 T-DRY(尽量不重复自己)

  • 坚持 DRY(Don’t Repeat Yourself,不重复自己)。
  • 避免过度 DRY,以致牺牲了阅读性。

    虽然 DRY 很重要,但如果要以牺牲 LIFT 的其它原则为代价,那就不值得了。 这也就是为什么它被称为 T-DRY。 例如,把组件命名为 hero-view.component.html 是多余的,因为带有 .html 扩展名的文件显然就是一个视图 (view)。 但如果它不那么显著,或不符合常规,就把它写出来。

4.6 共享特性模块

  • 坚持在 shared 目录中创建名叫 SharedModule 的特性模块(例如在 app/shared/shared.module.ts 中定义 SharedModule)。
  • 坚持在共享模块中声明那些可能被特性模块引用的可复用组件、指令和管道。
  • 考虑把可能在整个应用中到处引用的模块命名为 SharedModule
  • 考虑不要在共享模块中提供服务。服务通常是单例的,应该在整个应用或一个特定的特性模块中只有一份。 不过也有例外,比如,在下面的范例代码中,注意 SharedModule 提供了 FilterTextService。这里可以这么做,因为该服务是无状态的,也就是说,该服务的消费者不会受到这些新实例的影响。
  • 坚持在 SharedModule 中导入所有模块都需要的资产(例如 CommonModule 和 FormsModule)。
  • 坚持在 SharedModule 中声明所有组件、指令和管道。
  • 坚持从 SharedModule 中导出其它特性模块所需的全部符号。
  • 避免在 SharedModule 中指定应用级的单例服务提供商。如果是刻意要得到多个服务单例也行,不过还是要小心。

4.7 核心特性模块

TODO