1. 避免使用非严格模式
不良习惯示例
在tsconfig.json中不启用严格模式:
{
"compilerOptions": {
"target": "ES2015",
"module": "commonjs"
}
}
推荐做法
启用严格模式:
{
"compilerOptions": {
"target": "ES2015",
"module": "commonjs",
"strict": true
}
}
原因分析
在现有代码库中引入更严格规则需花费时间。
弊端说明
更严格规则利于未来代码变更,修复代码的时间投入将有回报,后续处理代码库时也更有益。
2. 摒弃用||定义默认值
不良习惯示例
使用||为可选值提供回退:
function createBlogPost(text: string, author: string, date?: Date) {
return {
text: text,
author: author,
date: date || new Date()
}
推荐做法
使用新的??运算符,或在参数级别定义回退值。
function createBlogPost(text: string, author: string, date: Date = new Date()) {
return {
text: text,
author: author,
date: date
}
原因分析
??运算符去年才引入,在长函数中使用值时,难将其设为参数默认值。
弊端说明
??仅在值为null或undefined时回退,与||不同。若函数过长,可考虑拆分并在开头定义默认值。
3. 勿用any作为类型
不良习惯示例
不确定数据结构时使用any:
async function loadProducts(): Promise<Product[]> {
const response = await fetch('https://api.mysite.com/products');
const products: any = await response.json();
return products;
推荐做法
几乎在所有将类型定义为any的情况下,应定义为unknown。
async function loadProducts(): Promise<Product[]> {
const response = await fetch('https://api.mysite.com/products');
const products: unknown = await response.json();
return products as Product[];
原因分析
any方便,基本禁用所有类型检查,官方类型定义中也常用(如response.json()被定义为Promise<any>)。
弊端说明
使用any会跳过所有类型检查,导致难以捕获的错误,只有类型结构假设与运行时代码相关时才会出错。
4. 减少强制类型断言val as SomeType
不良习惯示例
强制告知编译器无法推断的类型:
async function loadProducts(): Promise<Product[]> {
const response = await fetch('https://api.mysite.com/products');
const products: unknown = await response.json();
return products as Product[];
推荐做法
使用类型守卫。
function isArrayOfProducts(obj: unknown): obj is Product[] {
return Array.isArray(obj) && obj.every(isProduct);
}
function isProduct(obj: unknown): obj is Product {
return obj!= null && typeof (obj as Product).id === 'string';
}
async function loadProducts(): Promise<Product[]> {
const response = await fetch('https://api.mysite.com/products');
const products: unknown = await response.json();
if (!isArrayOfProducts(products)) {
throw new TypeError('Received malformed products API response');
}
return products;
原因分析
从JavaScript转换为TypeScript时,现有代码库常对类型做假设,无法自动推断,用as SomeOtherType可加快转换,无需放宽tsconfig设置。
弊端说明
即使断言当前正确,移动代码时可能改变。类型守卫确保检查显式。
5. 避免在测试中使用as any
不良习惯示例
编写测试时创建不完整模拟对象:
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
}
test('createEmailText returns text that greats the user by first name', () => {
const user: User = {
firstName: 'John'
} as any;
expect(createEmailText(user)).toContain(user.firstName);
});
推荐做法
如需模拟测试数据,将模拟逻辑移到模拟对象旁并使其可重用。
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
}
class MockUser implements User {
id = 'id';
firstName = 'John';
lastName = 'Doe';
email = 'john@doe.com';
}
test('createEmailText returns text that greats the user by first name', () => {
const user = new MockUser();
expect(createEmailText(user)).toContain(user.firstName);
});
原因分析
在测试覆盖率低的代码库中编写测试时,常遇复杂大数据结构,对特定功能仅需部分属性,短期忽略其他属性更轻松。
弊端说明
不创建模拟对象后续会有麻烦,如属性变化需在多处更改。被测试代码可能依赖之前不重要的属性,导致所有相关测试需更新。
6. 优化可选属性使用
不良习惯示例
将有时存在有时不存在的属性标记为可选:
interface Product {
id: string;
type: 'digital' | 'physical';
weightInKg?: number;
sizeInMb?: number;
推荐做法
显式建模属性存在组合。
interface Product {
id: string;
type: 'digital' | 'physical';
}
interface DigitalProduct extends Product {
type: 'digital';
sizeInMb: number;
}
interface PhysicalProduct extends Product {
type: 'physical';
weightInKg: number;
}
原因分析
标记属性为可选更简单,代码量少,需对产品有深入理解,产品假设变化时可能限制代码使用。
弊端说明
类型系统优势在于用编译时检查替代运行时检查。更明确的类型定义可在编译时发现易忽略错误,如确保DigitalProduct有sizeInMb属性。
7. 避免单字母泛型命名
不良习惯示例
用单字母命名泛型:
function head<T>(arr: T[]): T | undefined {
return arr[0];
推荐做法
使用完整描述性类型名称。
function head<Element>(arr: Element[]): Element | undefined {
return arr[0];
原因分析
官方文档使用单字母名称,输入快,按单字母比写完整名称思考少。
弊端说明
泛型类型变量同其他变量,单字母变量名难理解,放弃了用变量名描述技术细节的机会,不利于代码阅读和理解。
8. 避免非布尔值的布尔检查
不良习惯示例
通过将值直接传给if语句检查值是否定义:
function createNewMessagesResponse(countOfNewMessages?: number) {
if (countOfNewMessages) {
return `You have ${countOfNewMessages} new messages`;
}
return 'Error: Could not retrieve number of new messages';
推荐做法
显式检查关心的条件。
function createNewMessagesResponse(countOfNewMessages?: number) {
if (countOfNewMessages!== undefined) {
return `You have ${countOfNewMessages} new messages`;
}
return 'Error: Could not retrieve number of new messages';
原因分析
简短编写检查看似简洁,避免思考实际检查内容。
弊端说明
应思考实际检查内容,如上述示例对countOfNewMessages为0的处理不同。
9. 不使用双感叹号运算符转换布尔值
不良习惯示例
将非布尔值转换为布尔值:
function createNewMessagesResponse(countOfNewMessages?: number) {
if (!!countOfNewMessages) {
return `You have ${countOfNewMessages} new messages`;
}
return 'Error: Could not retrieve number of new messages';
推荐做法
显式检查关心的条件。
function createNewMessagesResponse(countOfNewMessages?: number) {
if (countOfNewMessages!== undefined) {
return `You have ${countOfNewMessages} new messages`;
}
return 'Error: Could not retrieve number of new messages';
原因分析
对部分人而言,!!像是JavaScript入门标志,简短简洁,若习惯则知其用途,是转换布尔值的快捷方式,在代码库中假值语义区分不明确时常用。
弊端说明
使用!!掩盖代码真实含义,对新开发者难理解,易引入微妙错误,如countOfNewMessages为0时的问题仍存在。
10. 不使用!= null同时检查null和undefined
不良习惯示例
!= null可同时检查null和undefined:
function createNewMessagesResponse(countOfNewMessages?: number) {
if (countOfNewMessages!= null) {
return `You have ${countOfNewMessages} new messages`;
}
return 'Error: Could not retrieve number of new messages';
推荐做法
显式检查关心的条件。
function createNewMessagesResponse(countOfNewMessages?: number) {
if (countOfNewMessages!== undefined) {
return `You have ${countOfNewMessages} new messages`;
}
return 'Error: Could not retrieve number of new messages';
原因分析
多数linting规则集对!= null做例外处理,若代码库中null和undefined无明确区别,!= null可缩短检查。
弊端说明
在TypeScript严格模式下,null可成为有价值的语言工具,如可定义user.firstName === null表示用户无名字,user.firstName === undefined表示未询问过用户,user.firstName === ''表示名字为空字符串,!= null模糊了这种区分。