Angular 18+ 高级教程 – Naming Conversion
前言
命名规范对项目维护是很重要的. Angular 对项目的渗透很大的, 必须做好命名规范, 不然会很乱.
InjectionToken
InjectionToken = UPPER_SNAKE_CARE
const SERVICE_CONFIG = new InjectionToken('ServiceConfig');
variable name 用 UPPER_SNAKE_CARE
InjectionToken 的参数用 PascalCase 或者 UPPER_SNAKE_CARE
Attribute and Property
element attribute and property name = camelCase
angular.io example 有 camelCase 和 kebab-case,不过我看绝大部分都是用 camelCase 居多。
<bank-account bankName="RBC" account-id="4747"></bank-account>
Event
event handler method
如果没有一个具体的执行名字,请不要放 (click)="onClick()" 放 "handleClick()"。
ValidationErrors
ValidationErrors key 用 camelCase
from Angular Docs
不过 built-in 的 minLength 和 maxLength 的 key 是 lowercase 'minlength' 和 'maxlength' 哦,奇葩。
Directive Selector
Angular CDK Derective selector 用 kebab-case 还是 camelCase?
CdkScrollable 指令 kebab-case 和 camelCase 都能接受。
CdkVirtualScrollableElement 指令只能接受 camelCase
CdkHeaderCell 指令只能接受 kebab-case。
显然,它们没有统一规范。不过通常 element 是用 kebab-case,attribute 用 camelCase。遇到例外比如 (th[cdk-header-cell]) 也没办法,只能 follow 它。
组件 attribute selector
通常组件是 tag,指令通常是 attribute。
<my-component myDirective></my-component>
但这也只是通常。
许多情况下组件也可能是 attrbute,这种情况下要用 kebab-case 还是 camelCase 呢?
我觉得像 Angular Material Button 这样使用 kebab-case 挺好的。
当组件的 selector 是 attribute 时使用 kebab-case。
@HostBinding and @HostListener Decorator
官网的 guide 是叫我们尽量用 @HostBinding Decorator 对比在 @Directive host property
但是 Angular Material 一直没有 follow 这个 guide
如今 Angular 正在去 Decorator 化,所以我建议使用 @Directive host property。
Animation CSS Property
依据官网教程,使用 camelCase
我觉得 camelCase 比 kebab-case 好,因为 Web Animation API 是只能使用 camelCase,这样比较统一。
Animation Trigger Name
下面这些是从 Angular Material 源码中抄出来的 Trigger Name:
- detailExpand
- panelAnimation
- state
- transformPanel
- fadeInCalendar
- dialogContainer
- indicatorRotate
- bodyExpansion
- transitionMessages
- transformMenu
- fadeInItems
- transformPanelWrap
- transformPanel
- transform
- indicator
- leftPointer
- rightPointer
- arrowOpacity
- arrowPosition
- allowChildren
- horizontalStepTransition
- verticalStepTransition
- translateTab
没有统一规范,但大部分有 2 个特性
-
对象
比如 panel, Calendar, dialog, body, Messages, Menu, Items 等等
有时候在前面,有时候在后面
-
动作
比如 Expand, transform, fadeIn, Rotate 等等
有时候在前面,有时候在后面,有时候只有对象没有动作。
当然还有一些比较直接的:panelAnimation,state。
我个人的规范是:
-
如果这个 trigger 会被用在多个 element 上,那取个动作名字就好了。
-
如果是用在指定 element 上,最好配个对象名字。
-
如果动作不好取,就直接叫 panelAnimation 或者 panelAnimationState 就好了。
x, y, top, right, bottom, left, vertical, horizontal
vertical 和 horizontal 的抽象叫 orientation (follow Angular Material)
x, y 抽象叫 axis (轴的意思,follow Angular Material)
top, right, bottom, left 抽象叫 position 或 direction (follow Angular Material)
exportAs
参考 Angular Material
指令和组件都可能会有 exportAs,
exportAs 用的是 camelCase,通常名字就和 element tag 或 attribute 一样。
String literal union types
参考 Angular 和 Material 源码
各种 case styles 都有,但绝大多数是 lowercase single word。
通常这些 string literal 会被用在 property 和 class name。
property 是 camelCase,class name 是 kebab-case 所以用这 2 种 case styles 就挺安全的了。
如果遇到冲突,我们可以做一些 conversion。
像这样:
//#region combineToCamelCase export function combineToCamelCase<T1 extends string, T2 extends string>( v1: T1, v2: T2, ): `${Uncapitalize<T1>}${Capitalize<T2>}`; export function combineToCamelCase<T1 extends string, T2 extends string, T3 extends string>( v1: T1, v2: T2, v3: T3, ): `${Uncapitalize<T1>}${Capitalize<T2>}${Capitalize<T3>}`; export function combineToCamelCase<T1 extends string, T2 extends string, T3 extends string, T4 extends string>( v1: T1, v2: T2, v3: T3, v4: T4, ): `${Uncapitalize<T1>}${Capitalize<T2>}${Capitalize<T3>}${Capitalize<T4>}`; export function combineToCamelCase(...values: string[]): string { return values .map((value, index) => index === 0 ? value.substring(0, 1).toLowerCase() + value.substring(1) : value.substring(0, 1).toUpperCase() + value.substring(1), ) .join(''); } //#endregion //#region combineToPascalCase export function combineToPascalCase<T1 extends string, T2 extends string>( v1: T1, v2: T2, ): `${Capitalize<T1>}${Capitalize<T2>}`; export function combineToPascalCase<T1 extends string, T2 extends string, T3 extends string>( v1: T1, v2: T2, v3: T3, ): `${Capitalize<T1>}${Capitalize<T2>}${Capitalize<T3>}`; export function combineToPascalCase<T1 extends string, T2 extends string, T3 extends string, T4 extends string>( v1: T1, v2: T2, v3: T3, v4: T4, ): `${Capitalize<T1>}${Capitalize<T2>}${Capitalize<T3>}${Capitalize<T4>}`; export function combineToPascalCase(...values: string[]): string { return values.map(value => value.substring(0, 1).toUpperCase() + value.substring(1)).join(''); } //#endregion //#region combineToKebabCase export function combineToKebabCase<T1 extends string, T2 extends string>( v1: T1, v2: T2, ): `${Lowercase<T1>}-${Lowercase<T2>}`; export function combineToKebabCase<T1 extends string, T2 extends string, T3 extends string>( v1: T1, v2: T2, v3: T3, ): `${Lowercase<T1>}-${Lowercase<T2>}-${Lowercase<T3>}`; export function combineToKebabCase<T1 extends string, T2 extends string, T3 extends string, T4 extends string>( v1: T1, v2: T2, v3: T3, v4: T4, ): `${Lowercase<T1>}-${Lowercase<T2>}-${Lowercase<T3>}-${Lowercase<T4>}`; export function combineToKebabCase(...values: string[]): string { return values.map(value => value.toLowerCase()).join('-'); } //#endregion
我个人的习惯是这样
1. 如果用于 property 就 camelCase,用于 class, id 就 kebab-case。
2. 如果 union 都是单词,首选 lowercase。
3. 如果跟后端有关联,会考虑使用 CamelCase,因为 TS 的 string literal union types 对应 C# 就是 enum,而 enum 是 CamelCase。
4. 如果单纯用来表达不同类型,camelCase 和 kebab-case 之前我会选 kebab-case,因为可读性高一点。
Change or Changes?
Angular Material 在写 @Output 时是用 xxxChange
但 xxxChanges 也有很多地方会使用哦
😂
keydown or keyDown?
Angular Material 源码中很少使用 keyDown (camelCase)。
keydown (lowercase) 就比较常见了
不纠结,跟它吧。
Class/File name 要 ends with 种类吗?
Angular 团队最近在做一个 RFC – An updated style guide for the year 2024。
里面其中一个比较火的话题是
File / Class name 的结尾是否要表明其种类,比如它是 Component, Directive, Service 还是其它。
我们参考 Angular Material 的话
显然,它是没有写种类的。
Angular 也同样是没有
虽然它们自己不用,但这不代表其它人也适合不用。要知道,Angular Material 是 UI 库,Angular 是前端 Framework,这两个项目都是针对特殊场景的。
倘若是业务型项目,区分种类可以让我们更一目了然。
比如说
product.ts (或者 product.entity.ts) 代表纯 class,对应后端的 product entity (比如 database 数据)
product.service.ts 代表处理 product 的服务,比如 CRUD 请求
product.page.ts 代表 product 页面
product.component.ts 代表 product 组件
另外,明确分类还能让 IDE 显示不同颜色的 icon
这样五颜六色,看上去多赏心悦目丫,偶然瞄到一眼,心情也舒服。
那为什么 Angular 要改掉这个 "guide" 呢?
除了它们自己不用也没关系以外,一个重要的原因是
Angular 正在往其它框架靠拢,希望在 Template 直接写 class name 作为组件 <Product />,而不像现在使用 selector <product />。
倘若 class name ends with 种类就会变成 <ProductComponent />,这样就很白目啊,因为在这个特殊的场合,"Component" 完全是多余的,这才是他们背后的想法。
老实说,这一批 Angular 团队总是人前一套人后一套,很让人反感。明明就是为了要抓新用户才靠拢 React, Vue,又怕得罪老用户,总找一些奇奇怪怪的理由🙄。无言...