Hi my new friend!

TypeScript 最佳实践与编码规范

Scroll down

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>>; // string

9. 正确处理函数重载

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 的最佳实践包括:

核心原则:

  1. 显式类型声明 - 提高代码可读性
  2. 严格模式 - 启用所有严格检查
  3. 善用类型系统 - 联合类型、交叉类型、泛型等
  4. 类型守卫 - 确保类型安全
  5. 只读属性 - 保护数据不被修改

高级特性: 6. ✅ 映射类型 - 灵活转换类型 7. ✅ 条件类型 - 类型级别的逻辑判断 8. ✅ 模板字符串类型 - 类型安全的字符串操作

TypeScript 5.x 新特性: 9. ✅ const 类型参数 - 更精确的类型推断 10. ✅ satisfies 操作符 - 类型检查 + 精确类型 11. ✅ 新装饰器标准 - 更强大的元编程 12. ✅ using 声明 - 自动资源管理 13. ✅ 改进的类型推断 - 更智能的类型系统

遵循这些实践,可以充分发挥 TypeScript 的优势,编写更安全、更易维护的代码。

🔗 相关文章

📖 参考资源

  • 本文作者:Gavin
  • 本文链接:
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
其他文章
cover
React 性能优化实战指南
  • 25-06-22
  • 06:30
  • 前端开发
cover
Webpack 性能优化完全指南
  • 25-05-15
  • 08:10
  • 前端开发