TypeScript 最佳实践与编码规范
📖 阅读时间:35分钟 | 难度:⭐⭐⭐⭐ 高级 | 更新日期:2025-01-26
TypeScript 为 JavaScript 带来了强大的类型系统,但要充分发挥其优势,需要遵循一些最佳实践。
1. 类型定义优先于类型推断
虽然 TypeScript 有强大的类型推断,但显式声明类型可以提高代码可读性。
typescript
// ❌ 不够明确
function processUser(user) {
return user.name.toUpperCase();
}
// ✅ 类型清晰
interface User {
id: number;
name: string;
email: string;
}
function processUser(user: User): string {
return user.name.toUpperCase();
}2. 使用 interface 而非 type(除非必要)
对于对象类型,优先使用 interface,因为它支持声明合并且错误提示更友好。
typescript
// ✅ 推荐使用 interface
interface User {
id: number;
name: string;
}
interface User {
age?: number; // 声明合并
}
// 仅在需要联合类型、交叉类型等时使用 type
type Status = 'pending' | 'success' | 'error';
type Result<T> = { data: T } | { error: string };3. 善用联合类型和类型守卫
typescript
type Success = { status: 'success'; data: any };
type Error = { status: 'error'; message: string };
type Loading = { status: 'loading' };
type ApiResponse = Success | Error | Loading;
function handleResponse(response: ApiResponse) {
// 类型守卫
if (response.status === 'success') {
console.log(response.data); // TypeScript 知道这里有 data
} else if (response.status === 'error') {
console.error(response.message); // TypeScript 知道这里有 message
} else {
console.log('加载中...');
}
}
// 自定义类型守卫
function isError(response: ApiResponse): response is Error {
return response.status === 'error';
}
if (isError(response)) {
console.error(response.message);
}4. 使用 readonly 保护数据
typescript
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
const config: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
};
// config.apiUrl = 'https://new-api.com'; // ❌ 错误:无法分配到 "apiUrl" ,因为它是只读属性
// 对于数组
const numbers: readonly number[] = [1, 2, 3];
// numbers.push(4); // ❌ 错误:类型"readonly number[]"上不存在属性"push"
// 使用 ReadonlyArray
const items: ReadonlyArray<string> = ['a', 'b', 'c'];5. 使用严格的 null 检查
在 tsconfig.json 中启用 strictNullChecks:
json
{
"compilerOptions": {
"strictNullChecks": true
}
}typescript
interface User {
name: string;
email?: string; // 可选属性
}
function sendEmail(user: User) {
// ❌ 错误:对象可能为 "undefined"
// user.email.toLowerCase();
// ✅ 正确:使用可选链
user.email?.toLowerCase();
// ✅ 正确:使用类型守卫
if (user.email) {
user.email.toLowerCase();
}
// ✅ 正确:使用空值合并
const email = user.email ?? 'default@example.com';
}6. 泛型的正确使用
typescript
// 基础泛型
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity('hello'); // 类型推断
// 约束泛型
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
logLength('hello'); // ✅
logLength([1, 2, 3]); // ✅
// logLength(123); // ❌ 错误:类型"number"的参数不能赋给类型"HasLength"的参数
// 泛型工具类型
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 实际应用
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>; // 所有属性可选
type UserPreview = Pick<User, 'id' | 'name'>; // 只选择某些属性7. 使用映射类型
typescript
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// 将所有属性变为可选
type PartialResponse<T> = {
[P in keyof ApiResponse<T>]?: ApiResponse<T>[P];
};
// 将所有属性变为只读
type ReadonlyResponse<T> = {
readonly [P in keyof ApiResponse<T>]: ApiResponse<T>[P];
};
// 实用工具类型
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
interface User {
name: string;
age: number;
}
type NullableUser = Nullable<User>;
// 等同于
// {
// name: string | null;
// age: number | null;
// }8. 条件类型的高级用法
typescript
// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 实用的条件类型
type NonNullable<T> = T extends null | undefined ? never : T;
type ExtractString<T> = T extends string ? T : never;
type OnlyStrings = ExtractString<string | number | boolean>; // string
// infer 关键字
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { name: 'John', age: 30 };
}
type User = ReturnType<typeof getUser>; // { name: string; age: number; }
// 提取 Promise 的值类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string9. 正确处理函数重载
typescript
// 函数重载
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'button'): HTMLButtonElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
const div = createElement('div'); // 类型为 HTMLDivElement
const span = createElement('span'); // 类型为 HTMLSpanElement
// 更灵活的方式:使用泛型
interface ElementMap {
div: HTMLDivElement;
span: HTMLSpanElement;
button: HTMLButtonElement;
}
function createElementGeneric<K extends keyof ElementMap>(tag: K): ElementMap[K] {
return document.createElement(tag) as ElementMap[K];
}10. 使用 const assertions
typescript
// 不使用 const assertion
const config = {
api: 'https://api.example.com',
timeout: 5000,
};
// config 的类型为 { api: string; timeout: number; }
// 使用 const assertion
const configConst = {
api: 'https://api.example.com',
timeout: 5000,
} as const;
// configConst 的类型为 { readonly api: "https://api.example.com"; readonly timeout: 5000; }
// 对数组使用
const colors = ['red', 'green', 'blue'] as const;
// colors 的类型为 readonly ["red", "green", "blue"]
type Color = typeof colors[number]; // "red" | "green" | "blue"11. 模块声明和命名空间
typescript
// 为第三方库添加类型声明
declare module 'my-library' {
export function doSomething(value: string): void;
}
// 扩展全局类型
declare global {
interface Window {
myCustomProperty: string;
}
}
// 使用命名空间组织代码
namespace Utils {
export namespace String {
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
export namespace Array {
export function unique<T>(arr: T[]): T[] {
return [...new Set(arr)];
}
}
}
Utils.String.capitalize('hello');
Utils.Array.unique([1, 2, 2, 3]);12. 装饰器(实验性功能)
typescript
// 启用装饰器:tsconfig.json 中设置 "experimentalDecorators": true
// 类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// 方法装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args:`, args);
return originalMethod.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}13. tsconfig.json 最佳配置
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}TypeScript 5.x 新特性
1. const 类型参数 (TS 5.0)
typescript
// 之前需要使用 as const
function makeArray<T>(items: T[]) {
return items
}
const arr1 = makeArray(['a', 'b']) // string[]
// TS 5.0: 使用 const 类型参数
function makeArrayConst<const T>(items: T[]) {
return items
}
const arr2 = makeArrayConst(['a', 'b']) // readonly ["a", "b"]2. satisfies 操作符 (TS 4.9+)
确保类型符合约束,同时保留精确类型。
typescript
type Colors = 'red' | 'green' | 'blue'
// ❌ 使用类型断言会丢失精确类型
const palette1: Record<Colors, string | number[]> = {
red: '#ff0000',
green: [0, 255, 0],
blue: '#0000ff'
}
palette1.red.toUpperCase() // 错误:string | number[] 上不存在 toUpperCase
// ✅ 使用 satisfies 保留精确类型
const palette2 = {
red: '#ff0000',
green: [0, 255, 0],
blue: '#0000ff'
} satisfies Record<Colors, string | number[]>
palette2.red.toUpperCase() // ✅ TypeScript 知道 red 是 string
palette2.green.push(128) // ✅ TypeScript 知道 green 是 number[]3. 装饰器元数据 (TS 5.0)
新的装饰器标准,不再需要 experimentalDecorators。
typescript
// 类装饰器
function logged<T extends { new (...args: any[]): {} }>(
value: T,
context: ClassDecoratorContext
) {
return class extends value {
constructor(...args: any[]) {
super(...args)
console.log(`Creating instance of ${context.name}`)
}
}
}
@logged
class Person {
name: string
constructor(name: string) {
this.name = name
}
}
// 方法装饰器
function bound(
value: Function,
context: ClassMethodDecoratorContext
) {
const methodName = String(context.name)
context.addInitializer(function () {
;(this as any)[methodName] = (this as any)[methodName].bind(this)
})
}
class MyClass {
message = 'Hello'
@bound
greet() {
console.log(this.message)
}
}
const obj = new MyClass()
const greet = obj.greet
greet() // 正常工作,this 已绑定4. 支持 import type 和 export type (TS 3.8+)
typescript
// 仅导入类型,不会在运行时保留
import type { User } from './types'
import { fetchUser } from './api' // 运行时导入
// 混合导入
import { type User, fetchUser } from './module'
// 导出类型
export type { User, Admin } from './types'5. 模板字符串类型增强 (TS 5.0+)
typescript
// 更强大的模板字符串类型
type EventName<T extends string> = `${T}Changed`
type PersonEvent = EventName<'name' | 'age'> // "nameChanged" | "ageChanged"
// 实际应用:类型安全的事件系统
type Events = {
click: { x: number; y: number }
focus: { element: HTMLElement }
input: { value: string }
}
type EventNames = keyof Events
type EventHandlers = {
[K in EventNames as `on${Capitalize<K>}`]: (event: Events[K]) => void
}
// 结果类型:
// {
// onClick: (event: { x: number; y: number }) => void
// onFocus: (event: { element: HTMLElement }) => void
// onInput: (event: { value: string }) => void
// }6. 使用 using 声明进行资源管理 (TS 5.2)
typescript
// 实现 Disposable 接口
class FileHandle {
private file: File
constructor(file: File) {
this.file = file
}
read() {
// 读取文件
}
[Symbol.dispose]() {
// 自动清理资源
console.log('Closing file')
}
}
function processFile() {
using file = new FileHandle(myFile)
file.read()
// 函数结束时自动调用 Symbol.dispose
}7. 改进的类型推断
typescript
// TS 5.0: 更好的数组推断
const arr = [1, 2, 3]
const doubled = arr.map(x => x * 2) // number[],不再是 any[]
// TS 5.1: 更好的函数返回类型推断
function getUser() {
if (Math.random() > 0.5) {
return { name: 'John', age: 30 }
}
return { name: 'Jane', age: 25 }
}
// 推断为: { name: string; age: number }
// TS 5.2: 更好的元组推断
const tuple = [1, 'hello', true] as const
// 推断为: readonly [1, "hello", true]8. 性能优化配置 (TS 5.0+)
json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler", // 新的模块解析策略
"verbatimModuleSyntax": true, // 更严格的模块语法
"allowImportingTsExtensions": true, // 允许导入 .ts 文件
"resolvePackageJsonExports": true, // 支持 package.json exports
"resolvePackageJsonImports": true, // 支持 package.json imports
"customConditions": ["development"] // 自定义条件
}
}总结
TypeScript 的最佳实践包括:
核心原则:
- ✅ 显式类型声明 - 提高代码可读性
- ✅ 严格模式 - 启用所有严格检查
- ✅ 善用类型系统 - 联合类型、交叉类型、泛型等
- ✅ 类型守卫 - 确保类型安全
- ✅ 只读属性 - 保护数据不被修改
高级特性: 6. ✅ 映射类型 - 灵活转换类型 7. ✅ 条件类型 - 类型级别的逻辑判断 8. ✅ 模板字符串类型 - 类型安全的字符串操作
TypeScript 5.x 新特性: 9. ✅ const 类型参数 - 更精确的类型推断 10. ✅ satisfies 操作符 - 类型检查 + 精确类型 11. ✅ 新装饰器标准 - 更强大的元编程 12. ✅ using 声明 - 自动资源管理 13. ✅ 改进的类型推断 - 更智能的类型系统
遵循这些实践,可以充分发挥 TypeScript 的优势,编写更安全、更易维护的代码。
🔗 相关文章
📖 参考资源
- 本文链接:
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。

