🧰 TypeScript 增强功能大纲 (相对于 ECScript)

TypeScript 作为 JavaScript 的超集,在 ECMAScript 标准之上引入了许多强大功能, primarily aimed at improving code maintainability, scalability, and developer experience through static type checking and other features. Here’s a outline of its key enhancements:

1. 静态类型系统 (Static Type System)

这是 TypeScript 最核心的增强,用于在编译时捕获类型错误。

1.1 类型注解 (Type Annotations)

  • 概念: 允许为变量、函数参数和返回值等显式指定类型。
  • 示例:
    let username: string = "John"; // 显式类型注解
    let age: number = 30;
    function greet(name: string): void { // 参数和返回值类型注解
      console.log(`Hello, ${name}`);
    }
    
  • ECMAScript 对比: JavaScript 是动态类型,无需也无法进行编译时类型注解。

1.2 类型推断 (Type Inference)

  • 概念: TypeScript 编译器能根据上下文自动推断变量的类型,无需总是显式注解。
  • 示例:
    let message = "Hello"; // TypeScript 推断 message 为 string 类型
    // message = 123; // 错误: 不能将类型“number”分配给类型“string”
    

1.3 基础类型增强

  • 概念: TypeScript 包含了 JavaScript 的所有基础类型,并增加了一些用于更精确类型检查的类型。
  • 增强类型:
    • any: 允许赋值为任意类型,本质上关闭了对该变量的类型检查。
    • unknown: 表示未知类型,比 any 更安全,因为在对 unknown 类型的值进行任何操作之前必须先进行类型检查。
    • void: 表示函数没有返回值。
    • never: 表示那些永不存在的值的类型(例如,总是抛出异常的函数或无限循环)。
    • 字面量类型: 允许将值作为类型,如 type Status = 'success' | 'failure'
      实际举例:
      =========
  1. any 类型 - 放弃类型检查
    any 类型是 TypeScript 中的一个“逃生舱口”。将一个变量标记为 any 类型意味着告诉 TypeScript 编译器跳过对该变量的类型检查。它可以被赋值为任何值,也可以被赋值给任何类型的变量

typescript

let notSure: any;

// 可以赋予任意类型的值
notSure = "I am a string";
notSure = 42;
notSure = true;
notSure = {};
notSure = null;
notSure = undefined;

// 也可以被赋值给任何类型的变量
let aString: string = notSure;
let aNumber: number = notSure;

// 可以对 any 类型的变量进行任何操作(即使它可能并不存在),编译时不会报错,但运行时可能出错
console.log(notSure.doesNotExist()); // 编译通过,运行时可能抛出 TypeError
notSure.toFixed(); // 编译通过,但如果 notSure 不是数字,运行时会出错

⚠️ 注意​: 虽然 any 提供了极大的灵活性,但它完全放弃了 TypeScript 的类型安全优势,应尽可能避免使用,或将其作为最后的手段
。过度使用 any 会让你的代码退化为“AnyScript”

  1. unknown 类型 - 类型安全的 any
    unknown 与 any 类似,可以接受任何类型的值
    。但关键区别在于:unknown 类型的变量在没有被类型断言或类型收窄之前,不能进行任何操作​(如方法调用、属性访问),也不能赋值给其他非 any 或 unknown 类型的变量
    。这迫使你必须先检查其类型,从而更安全。

typescript

let uncertainValue: unknown;

// 可以赋予任意类型的值
uncertainValue = "Hello World";
uncertainValue = [1, 2, 3];
uncertainValue = { name: "Alice" };

// 直接操作会报错(编译时)
// console.log(uncertainValue.toUpperCase()); // Error: Object is of type 'unknown'
// let aString: string = uncertainValue; // Error: Type 'unknown' is not assignable to type 'string'

// 必须进行类型检查(类型收窄)后才能安全使用
if (typeof uncertainValue === "string") {
  // 在这个块中,TypeScript 知道 uncertainValue 是 string 类型
  console.log(uncertainValue.toUpperCase()); // 现在可以安全调用字符串的方法
}

// 或者使用类型断言(你明确知道类型时)
console.log((uncertainValue as string).toUpperCase()); // 如果断言错误,运行时会出错

// unknown 类型只能赋值给 any 或 unknown 类型[2](@ref)
let anotherUnknown: unknown = uncertainValue; // OK
let anyValue: any = uncertainValue; // OK

​🎯 最佳实践​: 当你需要处理来自外部(如用户输入、API 响应)的未知类型数据时,优先使用 unknown 而不是 any,因为它要求你进行类型检查,从而保证了类型安全

  1. void 类型 - 没有返回值
    void 主要用于表示函数没有返回值​
    。如果一个函数没有 return 语句,或者返回 undefined,它的返回类型就是 void。

typescript

// 函数没有返回值
function greet(name: string): void {
  console.log(`Hello, ${name}!`);
}

// 返回 undefined 也是 void
function returnUndefined(): void {
  return undefined;
}

// 箭头函数表示没有返回值
const logMessage = (message: string): void => {
  console.log(message);
};

// 一个常见的误区:void 类型的变量
let unusable: void = undefined; // void 类型的变量只能赋予 undefined 或 null(在 strictNullChecks 为 false 时)
// unusable = "something"; // Error: Type 'string' is not assignable to type 'void'

​注意​: 与某些语言中的 void 不同,TypeScript 中的 void 不是一个真正的类型,它更是一个表示“没有有用返回值”的标记。

  1. never 类型 - 永不存在的值
    never 类型表示那些永远不会有返回值的函数的返回类型,或者永远不可能发生的值的类型
    。它是 TypeScript 类型系统中最底层的类型,是任何类型的子类型,但没有任何类型是 never 的子类型(除了 never 本身)

typescript

// 情况1:函数总是抛出错误
function throwError(message: string): never {
  throw new Error(message);
}

// 情况2:函数陷入死循环
function infiniteLoop(): never {
  while (true) {
    // do something
  }
}

// 情况3:用于穷举检查(Exhaustiveness Checking)

type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
  switch (shape) {
    case "circle":
      return Math.PI * 10 ** 2;
    case "square":
      return 10 * 10;
    // case "triangle":
    //   return (10 * 10) / 2;
    default:
      // 如果未来 Shape 类型增加了新成员(如 "hexagon"),但没有处理,则会产生编译错误
      // 因为 unexpected 会被推断为 never 类型,不能赋值给 never
      const unexpected: never = shape; // 如果所有 case 都已处理,这里 shape 的类型会是 never
      throw new Error(`Unexpected shape: ${unexpected}`);
  }
}

​关键点​: never 类型可以帮助你在编译时捕获到不可达的代码或未处理的分支,提升代码的健壮性。

=========

1.4 接口 (Interfaces)

  • 概念: 接口用于定义对象的形状(结构),作为代码契约,确保对象满足特定的结构要求。
  • 示例:
    interface User {
      id: string;
      name: string;
      email?: string; // 可选属性
      readonly registerTime: number; // 只读属性
    }
    function createUser(user: User): User {
      // ...
      return user;
    }
    
  • ECMAScript 对比: JavaScript 没有接口的概念。

1.5 枚举 (Enums)

  • 概念: 枚举允许定义一组命名常量。
  • 示例:
    enum Direction {
      Up,
      Down,
      Left,
      Right,
    }
    let dir: Direction = Direction.Up;
    
  • ECMAScript 对比: JavaScript 没有枚举类型,通常用对象模拟。

1.6 泛型 (Generics)

  • 概念: 泛型允许在定义函数、接口或类时不预先指定具体类型,而在使用时再指定,以提高组件的可复用性。
  • 示例:
    function identity<T>(arg: T): T { // 使用泛型
      return arg;
    }
    let output = identity<string>("myString"); // 输出类型为 string
    let output2 = identity<number>(42); // 输出类型为 number
    

1.7 高级类型 (Advanced Types)

  • 概念: TypeScript 提供了强大的类型操作能力,用于创建更复杂和灵活的类型定义。
  • 常见高级类型:
    • 联合类型: string | number (表示可以是多种类型之一)。
    • 交叉类型: TypeA & TypeB (表示同时满足多种类型)。
    • 类型别名: type MyType = ... 为类型创建新名字。
    • 类型守卫: 用于在条件语句中缩小类型的范围,如 typeof, instanceof, 或用户定义的类型守卫函数。
    • 映射类型: 基于旧类型创建新类型,如 Readonly<T>, Partial<T>
    • 条件类型: T extends U ? X : Y,根据类型关系选择类型。
      举例
      TypeScript 的高级类型功能非常强大,它们允许你创建灵活、精确且可重用的类型定义。下面我将为你详细解释这些高级类型,并提供相应的代码示例。

🧠 TypeScript 高级类型详解与代码示例

1. 联合类型 (Union Types)

联合类型允许一个值属于多种类型之一,使用 | 运算符定义。

// 基本用法:变量可以是多种类型之一
let id: string | number;
id = "ABC123"; // OK
id = 1001;     // OK
// id = true;  // Error: Type 'boolean' is not assignable to type 'string | number'

// 函数参数支持多种类型
function displayValue(value: string | number) {
  console.log(value);
}
displayValue("Hello"); // OK
displayValue(42);     // OK

// 处理可能为 null 或 undefined 的值
function getLength(input: string | null | undefined): number {
  if (input == null) { // 同时检查 null 和 undefined
    return 0;
  }
  return input.length; // 这里 TypeScript 知道 input 是 string
}

// 字面量联合类型,限制值为特定的几个选项
type Status = "active" | "inactive" | "pending";
let currentStatus: Status = "active"; // OK
// currentStatus = "disabled"; // Error: Type '"disabled"' is not assignable to type 'Status'

// 混合类型联合
type Answer = string | number | boolean;
function processAnswer(answer: Answer) {
  if (typeof answer === "string") {
    return answer.toUpperCase();
  } else if (typeof answer === "number") {
    return answer.toFixed(2);
  } else {
    return answer ? "YES" : "NO";
  }
}

2. 交叉类型 (Intersection Types)

交叉类型将多个类型合并为一个类型,使用 & 运算符定义,新类型拥有所有类型的成员。

// 基本用法:合并多个类型的属性
interface Person {
  name: string;
  age: number;
}

interface Employee {
  employeeId: string;
  department: string;
}

type EmployeePerson = Person & Employee;

const john: EmployeePerson = {
  name: "John Doe",
  age: 30,
  employeeId: "E123",
  department: "Engineering"
};

// 合并函数类型:表示函数重载
type StringProcessor = (input: string) => string;
type NumberProcessor = (input: number) => number;

type Processor = StringProcessor & NumberProcessor;

const processor: Processor = (input: any) => {
  if (typeof input === "string") {
    return input.toUpperCase();
  } else if (typeof input === "number") {
    return input * 2;
  }
  return input;
};

console.log(processor("hello")); // "HELLO"
console.log(processor(10));     // 20

// 与泛型结合创建可复用类型
type WithTimestamp<T> = T & { timestamp: Date };

function addTimestamp<T>(data: T): WithTimestamp<T> {
  return {
    ...data,
    timestamp: new Date()
  };
}

const userData = { name: "Alice", score: 100 };
const dataWithTime = addTimestamp(userData);
console.log(dataWithTime.timestamp); // 当前时间

3. 类型别名 (Type Aliases)

类型别名允许你为现有类型创建一个新名称,使用 type 关键字定义。

// 为基本类型创建别名
type UserID = string;
type Score = number;

// 复杂类型别名
type User = {
  id: UserID;
  name: string;
  email: string;
  score: Score;
};

// 函数类型别名
type StringTransformer = (input: string) => string;

const toUpper: StringTransformer = (str) => str.toUpperCase();

// 泛型类型别名
type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;

// 递归类型别名:表示树形结构
type TreeNode<T> = {
  value: T;
  left?: TreeNode<T>;
  right?: TreeNode<T>;
};

const binaryTree: TreeNode<number> = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4 },
    right: { value: 5 }
  },
  right: {
    value: 3,
    right: { value: 6 }
  }
};

4. 类型守卫 (Type Guards)

类型守卫是运行时检查,用于在条件块中缩小变量的类型范围。

// typeof 类型守卫
function processValue(value: string | number) {
  if (typeof value === "string") {
    // 在这个块中,value 被识别为 string 类型
    console.log(value.toUpperCase());
  } else {
    // 在这个块中,value 被识别为 number 类型
    console.log(value.toFixed(2));
  }
}

// instanceof 类型守卫
class Dog {
  bark() {
    console.log("Woof!");
  }
}

class Cat {
  meow() {
    console.log("Meow!");
  }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark(); // animal 被识别为 Dog
  } else {
    animal.meow(); // animal 被识别为 Cat
  }
}

// in 操作符类型守卫
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

function getArea(shape: Circle | Square) {
  if ("radius" in shape) {
    // shape 被识别为 Circle
    return Math.PI * shape.radius ** 2;
  } else {
    // shape 被识别为 Square
    return shape.sideLength ** 2;
  }
}

// 自定义类型守卫函数
function isCircle(shape: Circle | Square): shape is Circle {
  return shape.kind === "circle";
}

function calculateArea(shape: Circle | Square) {
  if (isCircle(shape)) {
    return Math.PI * shape.radius ** 2; // shape 被识别为 Circle
  } else {
    return shape.sideLength ** 2; // shape 被识别为 Square
  }
}

// 可辨识联合类型(Discriminated Unions)
type NetworkState =
  | { state: "loading"; progress: number }
  | { state: "success"; data: string }
  | { state: "error"; code: number; message: string };

function handleNetworkState(networkState: NetworkState) {
  switch (networkState.state) {
    case "loading":
      console.log(`Loading... ${networkState.progress}%`);
      break;
    case "success":
      console.log(`Data: ${networkState.data}`);
      break;
    case "error":
      console.error(`Error ${networkState.code}: ${networkState.message}`);
      break;
  }
}

5. 映射类型 (Mapped Types)

映射类型基于旧类型创建新类型,通过遍历现有类型的属性并对其进行转换。

// 基本映射类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Partial<T> = {
  [P in keyof T]?: T[P];
};

type Required<T> = {
  [P in keyof T]-?: T[P]; // -? 表示移除可选
};

interface User {
  name: string;
  age?: number;
  email: string;
}

type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;

// 自定义映射类型
type Stringify<T> = {
  [P in keyof T]: string;
};

type StringifiedUser = Stringify<User>;
// { name: string; age?: string; email: string }

// 使用 as 子句重映射键名
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

type UserGetters = Getters<User>;
// { getName: () => string; getAge?: () => number | undefined; getEmail: () => string }

// 过滤属性
type OnlyFunctions<T> = {
  [P in keyof T as T[P] extends Function ? P : never]: T[P];
};

interface Example {
  name: string;
  age: number;
  getName: () => string;
  setName: (name: string) => void;
}

type ExampleFunctions = OnlyFunctions<Example>;
// { getName: () => string; setName: (name: string) => void }

6. 条件类型 (Conditional Types)

条件类型根据类型关系选择类型,语法为 T extends U ? X : Y

// 基本条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// 从类型中提取特定类型
type ExtractString<T> = T extends string ? T : never;
type Filtered = ExtractString<string | number | boolean>; // string

// 排除特定类型
type ExcludeString<T> = T extends string ? never : T;
type NonString = ExcludeString<string | number | boolean>; // number | boolean

// infer 关键字:在条件类型中推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
  return { id: 1, name: "Alice" };
}

type UserReturnType = ReturnType<typeof getUser>; // { id: number; name: string }

// 提取数组元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type NumberArrayElement = ArrayElement<number[]>; // number
type MixedArrayElement = ArrayElement<(string | number)[]>; // string | number

// 提取 Promise 解析类型
type PromiseResult<T> = T extends Promise<infer U> ? U : never;
type NumberPromiseResult = PromiseResult<Promise<number>>; // number

// 分布式条件类型:当条件类型作用于联合类型时,会将条件应用到联合类型的每个成员
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]

// 递归条件类型:实现深度可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface NestedObject {
  level1: {
    level2: {
      value: string;
    };
  };
}

type PartialNested = DeepPartial<NestedObject>;
// {
//   level1?: {
//     level2?: {
//       value?: string;
//     };
//   };
// }

7. 综合应用示例

下面是一个综合运用多种高级类型的实际示例:

// API 响应处理
type ApiResponse<T> =
  | { status: "success"; data: T; timestamp: Date }
  | { status: "error"; code: number; message: string; details?: unknown }
  | { status: "loading"; progress: number };

// 用户类型
interface User {
  id: string;
  name: string;
  email: string;
  preferences?: {
    theme: "light" | "dark";
    notifications: boolean;
  };
}

// 使用映射类型创建用户更新类型
type UserUpdate = Partial<DeepPartial<User>>;

// 类型守卫函数
function isSuccessResponse<T>(response: ApiResponse<T>): response is { status: "success"; data: T; timestamp: Date } {
  return response.status === "success";
}

// 处理 API 响应
async function fetchUser(userId: string): Promise<ApiResponse<User>> {
  try {
    // 模拟 API 调用
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      return {
        status: "error",
        code: response.status,
        message: response.statusText
      };
    }
    
    const data = await response.json();
    return {
      status: "success",
      data,
      timestamp: new Date()
    };
  } catch (error) {
    return {
      status: "error",
      code: 500,
      message: "Network error",
      details: error
    };
  }
}

// 使用示例
async function getUserData(userId: string) {
  const response = await fetchUser(userId);
  
  if (isSuccessResponse(response)) {
    // 这里 response 被识别为 success 类型
    console.log(`User data: ${response.data.name}`);
    console.log(`Fetched at: ${response.timestamp.toISOString()}`);
    return response.data;
  } else if (response.status === "error") {
    // 这里 response 被识别为 error 类型
    console.error(`Error ${response.code}: ${response.message}`);
    throw new Error(response.message);
  } else {
    // 这里 response 被识别为 loading 类型
    console.log(`Loading... ${response.progress}%`);
    return null;
  }
}

这些高级类型是 TypeScript 强大类型系统的核心,它们允许你创建精确、灵活且可重用的类型定义,大大提高代码的类型安全性和可维护性。

2. 面向对象编程增强

TypeScript 提供了更完整和强大的面向对象编程支持。

2.1 访问修饰符 (Access Modifiers)

  • 概念: 控制类成员的可见性。
  • 类型:
    • public: (默认)公有,任何地方都可访问。
    • private: 私有,只能在类内部访问。
    • protected: 受保护,可在类内部及其子类中访问。
  • 示例:
    class Animal {
      public name: string;
      private secret: string;
      protected age: number;
      // ...
    }
    

2.2 抽象类 (Abstract Classes)

  • 概念: 抽象类作为其他类的基类,不能直接实例化。抽象类中的抽象方法必须在派生类中实现。
  • 示例:
    abstract class Department {
      constructor(public name: string) {}
      abstract printMeeting(): void; // 必须在派生类中实现
    }
    class AccountingDepartment extends Department {
      constructor() {
        super("Accounting and Auditing");
      }
      printMeeting(): void {
        console.log("The Accounting Department meets at 10am.");
      }
    }
    // let dept = new Department(); // 错误: 无法创建抽象类的实例
    let dept: Department = new AccountingDepartment(); // 允许对一个抽象子类进行引用
    
  • ECMAScript 对比: JavaScript 本身没有抽象类的概念。

2.3 参数属性 (Parameter Properties)

  • 概念: 在构造函数参数前使用访问修饰符(public, private, protected, readonly),可以同时声明并初始化成员属性,是一种简便写法。
  • 示例:
    class Octopus {
      constructor(readonly name: string, private readonly numberOfLegs: number) {
        // 无需再写 this.name = name;
      }
    }
    

3. 模块与命名空间

3.1 命名空间 (Namespaces)

  • 概念: 命名空间(旧称“内部模块”)用于在全局命名空间内对相关代码进行分组,避免命名冲突。
  • 示例:
    namespace Validation {
      export interface StringValidator {
        isAcceptable(s: string): boolean;
      }
      // ... 其他实现细节
    }
    let validator: Validation.StringValidator;
    
  • ECMAScript 对比: 现代 ES 模块(import/export)已成为组织代码的首选方式,但命名空间在特定场景(如声明文件)仍有其用途。

4. 工具与元编程

4.1 装饰器 (Decorators)

  • 概念: 装饰器是一种特殊类型的声明,可以附加到类声明、方法、访问符、属性或参数上,用于修改类的行为(目前仍是实验性特性,需在 tsconfig.json 中启用)。
  • 示例 (方法装饰器):
    function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      const originalMethod = descriptor.value;
      descriptor.value = function (...args: any[]) {
        console.log(`Calling ${propertyKey} with arguments: ${JSON.stringify(args)}`);
        return originalMethod.apply(this, args);
      };
      return descriptor;
    }
    class Calculator {
      @log
      add(x: number, y: number): number {
        return x + y;
      }
    }
    
  • ECMAScript 对比: JavaScript 装饰器仍处于提案阶段,TypeScript 提供了实验性支持。

5. 工具链与工程化支持

5.1 TypeScript 编译器 (TSC) 及配置

  • 概念: TypeScript 提供了强大的编译器 (tsc) 和丰富的配置选项 (tsconfig.json),用于控制编译过程、目标 JavaScript 版本、模块系统、严格性检查等。
  • 常见编译选项:
    • --strict: 启用所有严格类型检查选项。
    • --target: 指定编译输出的 ECMAScript 目标版本(如 ES5, ES2015, ES2022)。
    • --module: 指定模块代码生成策略(如 CommonJS, ES2015, NodeNext)。
    • --noImplicitAny: 禁止隐式的 any 类型。
    • --strictNullChecks: 启用严格的 nullundefined 检查。

5.2 声明文件 (Declaration Files)

  • 概念: 使用 .d.ts 文件为现有的 JavaScript 库提供类型信息,以便在 TypeScript 项目中安全地使用这些库。
  • 示例: 为第三方库编写类型声明。
    // my-library.d.ts
    declare module "my-library" {
      export function doSomething(value: string): void;
    }
    
  • ECMAScript 对比: JavaScript 无需也不支持类型声明文件。

📊 TypeScript 与 ECMAScript 关键特性对比

特性类别 TypeScript 功能 ECMAScript (JavaScript) 对应情况
变量类型 静态类型注解 (: type), 泛型, 枚举, 字面量类型, 元组 动态类型,无编译时类型注解
对象结构定义 接口 (interface), 类型别名 (type)
访问修饰符 (public, private, protected), 抽象类 (abstract), 参数属性 ES6 Class (无访问控制符和抽象类概念)
函数 函数类型注解, 函数重载 函数声明,无重载
模块化 命名空间 (namespace), 声明文件 (.d.ts) ES Module (import/export)
元编程 装饰器 (实验性) 无 (装饰器处于提案阶段)
错误检测 编译时类型错误 运行时错误

💡 教学提示

  1. 强调设计目的: TypeScript 的核心价值在于其静态类型系统,它旨在为大型应用开发提供更好的工具支持和可维护性,而不是引入全新的运行时特性。
  2. 渐进式采用: 向学生说明,TypeScript 是 JavaScript 的超集,任何合法的 JavaScript 代码都是合法的 TypeScript 代码,因此可以逐步将类型添加到现有的 JavaScript 项目中。
  3. 实践导向: 多展示如何在常见场景(如 React 组件、Node.js API 接口)中应用 TypeScript 的类型系统(定义 Props、接口请求/响应类型等)来提升代码质量。
  4. 工具链熟悉: 让学生熟悉 tsc 编译器的使用和 tsconfig.json 的配置,这是工程化实践中非常重要的一环。

更多推荐