2. 语法规范
2.1. 标识符
2.1.1. 命名规范
在 TypeScript 中,标识符只能使用 ASCII 码表中的字母、数字、下划线与 (
。因此,合法的标识符可以使用正则表达式 [\)\w]+
进行匹配。根据标识符的用途不同,使用的命名法也不同,如下表所示:
命名法 | 分类 |
---|---|
帕斯卡命名法(UpperCamelCase ) | 类、接口、类型、枚举、装饰器、类型参数 |
驼峰式命名法(lowerCamelCase ) | 变量、参数、函数、方法、属性、模块别名 |
全大写下划线命名法(CONSTANT_CASE ) | 全局常量、枚举值 |
全小写下划线命名法(constant_case ) | URL 参数名称 |
全小写中划线命名法(kebab-case ) | URL 路径名称 |
私有成员命名法(#ident ) | 不允许使用 |
例如:
- 必须 枚举值的声明
enum FieldNames {
NAME = "name",
TENANT_ID = "tenantId",
}
// 如果是组件可选属性值,可用全小写作为 key
enum ButtonSizes {
default = "default",
small = "small",
large = "large",
}
- 必须 常量的声明
// 常量对象方式一
const FIELD_NAMES = {
TENANT_ID: "tenantId",
} as const;
// 常量对象方式二
const FIELD_NAMES = {
tenantId: "tenantId",
} as const;
// 基本类型常量
const TENANT_ID = "tenantId";
// 类中的常量
class FieldName {
private static MAX_SIZE = 1024;
}
// 数组常量使用 as const 转为元组类型,禁止修改
const AllNames = ['Tom', 'Jerry'] as const;
如果强烈要求常量对象无法修改,则使用 Object.freeze(obj)
冻结对象。该冻结方式只能冻结一层,冻结全部需要更深层次的操作。
- 必须 接口的声明
interface FieldNameProps {
name: string;
tenantId: string;
}
- 必须 类的私有成员声明
class FieldName {
private name: string;
#name: string; // 禁止使用
}
类中 public/private/protect
区别:
public
:公有成员,可以被外部访问,默认情况下,所有成员都是 public。private
:私有成员,只能在类内部访问,不能被外部访问。protect
:保护成员,只能在类内部访问,可以被继承。
- 必须 类型参数声明
类型参数既可以使用单个大写字母(如
T
),也可以使用帕斯卡命名法(如UpperCamelCase
)
class FieldName<T> {
name: T;
constructor(name: T) {
this.name = name;
}
}
2.1.1.1. 应当 缩写
缩写应被视为一个词。例如,应使用 loadHttpUrl
,而非 loadHTTPURL
。平台有特殊要求的标识符例外,如 XMLHttpRequest
。
2.1.1.2. 应当 美元符号 $
一般情况下,标识符不应使用 $
,除非为了与第三方框架的命名规范保持一致。例如 jQuery
。
2.1.2. 应当 别名
在为一个已有的标识符创建具有局部作用域的别名时,别名的命名方式应当与现有的标识符和现有的命名规范保持一致
。声明别名时,应使用 const
(如果它是一个变量)或 readonly
(如果它是类里的一个字段)。
const {Foo} = SomeType;
const CAPACITY = 5;
class Teapot {
readonly BrewStateEnum = BrewStateEnum;
readonly CAPACITY = CAPACITY;
}
2.1.3. 命名风格
TypeScript
中的类型表达了丰富的信息,因此在起名时不应与类型中所携带的信息重复。太短的标识符往往不知所云。较长的标识符通常使内容更具可读性,但是也会降低可读性。
命名应该符合两个条件: 清晰
(知道它是什么)并且准确
(知道它不是什么)。下面有一些有用的指导方针:
- 省略那些变量类型非常明确的描述语
// 不好的, 类型声明已经告诉我们,它是 array 了
const namesArray: string[] = ['a', 'b', 'c'];
// 好的
const names: string[] = ["a", "b", "c"];
- 省略那些变量类型非常明确的描述语
// 过于具体的名字,很难读:
let finalBattleMostDangerousBossMonster;
let nonTypicalMonthlyPayments;
// 好的,假如没有会引起歧义的其它monsters或payments:
let boss;
let payments;
- 省略那些从上下文中可以得到的描述信息
class AnnualHolidaySale {
// 不好的,重复了上下文
annualSaleRebate;
promoteHolidaySale() {
}
}
class AnnualHolidaySale {
// 好的
rebate;
promote() {
}
}
- 省略那些可以到处使用的标识符 例如:data, state, amount, number, value, manager, engine, object, entity, instance, helper, util, broker, metadata, process, handle, context。 当然也有一些例外情况,此时要加入你自己的判断。过长的命名仍旧比太短的命名好。
2.1.4. 描述性命名
命名应当具有描述性且易于读者理解。不要使用对项目以外的用户而言含糊不清或并不熟悉的缩写,不要通过删减单词中的字母来强行创造缩写。
这一规则的例外是,对不超过十行的作用域中的变量,以及内部 API 的参数,可以使用短变量名(例如 i
、 j
等只有单个字母的变量名)。
2.2 注释与文档
2.2.1. 用 JSDoc 还是 注释?
TypesScript
中有两种类型的注释:JSDoc /** ... */
和普通注释 // ...
或者 /* ... */
。
- 对于文档,也就是用户应当阅读的注释,使用
/** JSDoc */
。 - 对于实现说明,也就是只和代码本身的实现细节有关的注释,使用
// 行注释
。
对于工具方法或公用程度比较高的组件
内的方法,方法注释必须使用 /** JSDoc */
。因为JSDoc 注释
能够为工具(例如编辑器或文档生成器)所识别,而普通注释只能供人阅读。
2.2.2. JSDoc 规范
JSDoc注释
一般应该放置在方法或函数声明之前,它必须以 /**
开始,以便由 JSDoc解析器
识别。其他任何以 /*
,/***
或者超过3个星号的注释,都将被 JSDoc解析器
忽略。
比较常用的有如下几个标签:
@param
:指定参数的名称和描述。
/**
* @param wording 需要说的句子
*/
function say(wording) {
console.log(wording);
}
@returns
:指定返回值。
/*
* @return 返回值描述
*/
@copyright
:代码的版权信息。@author
:代码的作者。@version
:代码的版本。@constructor
:指定构造函数的名称。
2.2.3. 省略对于 TypeScript
而言多余的注释
不应不应在 @param
或 @return
注释中声明类型,因为 TypeScript
本身已经包含了类型。不应在使用了 implements
、 enum
、 private
等关键字的地方添加 @implements
、 @enum
、 @private
等注释。
2.2.4. 不应使用 @override
不应不应在 TypeScript
代码中使用 @override
注释。 @override
并不会被编译器视为强制性约束,这会导致注释与实现上的不一致性。如果纯粹为了文档添加这一注释,反而令人困惑。
2.2.5. 注释必须言之有物
虽然大多数情况下文档对代码十分有益,但对于那些并不用于导出的符号,有时其函数或参数的名称与类型便足以描述自身了。
不应注释不应照抄参数类型和参数名,如下面的反面示例:
// 不要这样做!这个注释没有任何有意义的内容。
/** @param fooBarService Foo 应用的 Bar 服务 */
因此,只有当需要添加额外信息时才使用 @param
和 @return
注释,其它情况下直接省略即可。
/**
* 发送 POST 请求,开始煮咖啡
* @param amountLitres 煮咖啡的量,注意和煮锅的尺寸对应!
*/
brew(amountLitres: number, logger: Logger) {
// ...
}
2.2.6. 参数属性注释
通过为构造函数的参数添加访问限定符,参数属性同时创建了构造函数参数和类成员。例如,如下的构造函数:
class Foo {
constructor(private readonly bar: Bar) { }
}
为 Foo
类创建了 Bar
类型的成员 bar
。
如果要为这些成员添加文档,应使用 JSDoc
的 @param
注释,这样编辑器会在调用构造函数和访问属性时显示对应的文档描述信息。
/** 这个类演示了如何为参数属性添加文档 */
class ParamProps {
/**
* @param percolator 煮咖啡所用的咖啡壶。
* @param beans 煮咖啡所用的咖啡豆。
*/
constructor(
private readonly percolator: Percolator,
private readonly beans: CoffeeBean[]) {}
}
/** 这个类演示了如何为普通成员添加文档 */
class OrdinaryClass {
/** 下次调用 brew() 时所用的咖啡豆。 */
nextBean: CoffeeBean;
constructor(initialBean: CoffeeBean) {
this.nextBean = initialBean;
}
}
2.2.7. 将文档置于装饰器之前
必须文档、方法或者属性如果同时具有装饰器(例如 @Component
)和 JSDoc 注释
,应当将 JSDoc
置于装饰器之前。
/** 打印 "bar" 的组件。 */
@Component({
selector: 'foo',
template: 'bar',
})
export class FooComponent {}
禁止将 JSDoc 置于装饰器和被装饰的对象之间。
// 不要这样做!JSDoc 被放在装饰器 @Component 和类 FooComponent 中间了!
@Component({
selector: 'foo',
template: 'bar',
})
/** 打印 "bar" 的组件。 */
export class FooComponent {}
2.3 路由 URL 命名规范
必须对于前端路由定义规范如下:
- 路径中的所有单词都应该使用
小写字母
。 - 路径命名使用
kebab-case
命名规范。 - 参数名称命名使用
constant_case
或lowerCamelCase
命名规范。
示例参考如下:
const url = 'http://www.example.com/foo-bar?my_name=foo';
在搜索引擎中,它是把中划线当作一个空格来处理的,而下划线则是被忽略的,
例如seo-caipiao
会被读成seo
与caipiao
。这是比较友好的写法
2.4. 文件夹及文件命名规范
必须- 对于
页面
及组件
所属的文件夹名称统一使用UpperCamelCase
命名规范。 - 对于
hooks
、constants
、types
、models
、utils
、services
、styles
文件夹内文件名称统一使用lowerCamelCase
命名规范。 assets
内图片全部采用小写命名方式,优先选择单个单词命名,使用constant_case
命名规范。
2.5. 方法命名规范
应当- 对于组件内的方法命名使用
lowerCamelCase
命名规范。 - 自定义
hooks
的方法命名使用use
开头,使用lowerCamelCase
命名规范。 - 方法的命名应该是动词或动宾短语,应该避免使用名词或名词短语。
- 监听类型事件方法以
on
开头,例如onChange
,onFocus
等。 - 主动触发类型事件方法按照
handle + 名称(可选)+ 动词
的规则进行命名,例如handleClick
,handleFocus
等。
举例:
// 1、普通情况下,使用动词 + 名词形式
// bad
go、nextPage、show、open
// good
jumpPage、openCarInfoDialog
// 2、请求数据方法,以 data 结尾
// bad
takeData、confirmData、getList、postForm
// good
getListData、postFormData
// 3、单个动词的情况
init、refresh
常用动词:
动词 | 含义 | 返回值 |
---|---|---|
on | 监听某个事件 | 无返回值; |
handle | 主动触发某个事件 | 无返回值、返回是否设置成功或者返回链式对象; |
can | 判断是否可执行某个动作 (权限) | 函数返回一个布尔值。true:可执行;false:不可执行; |
has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值; |
is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个值;false:不为某个值; |
get | 获取某个值 | 函数返回一个非布尔值 |
set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象 |
2.6. 代码行数规范
必须 (此规则针对 aPaaS 项目)严格限制代码行数避免过长的文件,以保证代码的可读性。代码限制规则如下:
规则线 | 行数 | 说明 |
---|---|---|
合理线 | 0~800行 | 代码在合理范围内,不需要拆分 |
警告线 | 800~1000行 | 注意组件拆分,逻辑复用,考虑是否重构 |
禁止线 | 1000+行 | 及时考虑重构,代码 review 时讨论代码设计 |