TypeScript 增强功能大纲 (相对于 ECMAScript)
🧰 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'
。
实际举例:
=========
- 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”
。
- 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,因为它要求你进行类型检查,从而保证了类型安全
。
- 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 不是一个真正的类型,它更是一个表示“没有有用返回值”的标记。
- 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 类型,不能赋值给 neverconst unexpected: never = shape; // 如果所有 case 都已处理,这里 shape 的类型会是 neverthrow 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 和 undefinedreturn 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 被识别为 Circlereturn Math.PI * shape.radius ** 2;} else {// shape 被识别为 Squarereturn 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 {@logadd(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
: 启用严格的null
和undefined
检查。
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 ) |
元编程 | 装饰器 (实验性) | 无 (装饰器处于提案阶段) |
错误检测 | 编译时类型错误 | 运行时错误 |
💡 教学提示
- 强调设计目的: TypeScript 的核心价值在于其静态类型系统,它旨在为大型应用开发提供更好的工具支持和可维护性,而不是引入全新的运行时特性。
- 渐进式采用: 向学生说明,TypeScript 是 JavaScript 的超集,任何合法的 JavaScript 代码都是合法的 TypeScript 代码,因此可以逐步将类型添加到现有的 JavaScript 项目中。
- 实践导向: 多展示如何在常见场景(如 React 组件、Node.js API 接口)中应用 TypeScript 的类型系统(定义
Props
、接口请求/响应类型等)来提升代码质量。 - 工具链熟悉: 让学生熟悉
tsc
编译器的使用和tsconfig.json
的配置,这是工程化实践中非常重要的一环。