TypeScript 高级特性详解
引言
TypeScript 是 JavaScript 的超集,为 JavaScript 添加了静态类型检查和面向对象编程的能力。随着前端应用日益复杂,TypeScript 的高级特性变得越来越重要。本文将深入探讨 TypeScript 的几个核心高级特性,帮助开发者更好地理解和运用这些强大功能。
一、泛型编程(Generic Programming)
1.1 泛型基础概念
泛型(Generic)是 TypeScript 中最强大的特性之一,它允许我们创建可重用的组件,这些组件可以处理不同类型的数据而不丢失类型安全性。
typescript
// 基础泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用泛型函数
let output1 = identity<string>("myString");
let output2 = identity<number>(42);
let output3 = identity("myString"); // 类型推断
1.2 泛型约束
有时候我们需要对泛型参数施加一定的约束,确保传入的类型具有某些特定的属性或方法。
typescript
// 定义约束接口
interface Lengthwise {
length: number;
}
// 使用 extends 关键字添加约束
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在我们知道它有 .length 属性
return arg;
}
loggingIdentity("hello"); // 正确,字符串有 length 属性
loggingIdentity([1, 2, 3]); // 正确,数组有 length 属性
// loggingIdentity(3); // 错误,数字没有 length 属性
1.3 泛型类
泛型也可以应用于类,使类能够处理多种类型的值。
typescript
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zeroValue: T, addFn: (x: T, y: T) => T) {
this.zeroValue = zeroValue;
this.add = addFn;
}
}
let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myGenericNumber.add(5, 10)); // 15
let stringNumeric = new GenericNumber<string>("", (x, y) => x + y);
console.log(stringNumeric.add("hello", "world")); // "helloworld"
1.4 泛型接口
泛型接口可以描述那些跨越多种类型的通用调用签名。
typescript
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
1.5 多个泛型参数
函数和类可以接受多个泛型参数,提供更多灵活性。
typescript
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
console.log(swap(["hello", 123])); // [123, "hello"]
class KeyValuePair<TKey, TValue> {
private key: TKey;
private value: TValue;
constructor(key: TKey, value: TValue) {
this.key = key;
this.value = value;
}
getKey(): TKey {
return this.key;
}
getValue(): TValue {
return this.value;
}
}
1.6 泛型参数默认值
TypeScript 允许为泛型参数指定默认类型。
typescript
class DefaultGeneric<T = string> {
private value: T;
setValue(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
let defaultGeneric = new DefaultGeneric(); // T 默认为 string
let explicitGeneric = new DefaultGeneric<number>(); // 显式指定 T 为 number
1.7 泛型条件约束
结合条件类型,我们可以创建更加灵活的泛型约束。
typescript
// 只有当 T 是 string 或 number 时才允许
type StringOrNumberOnly<T> = T extends string | number ? T : never;
function processValue<T>(value: StringOrNumberOnly<T>): StringOrNumberOnly<T> {
return value;
}
processValue("hello"); // OK
processValue(42); // OK
// processValue(true); // Error: Type 'boolean' does not satisfy the constraint 'never'
二、条件类型和映射类型
2.1 条件类型基础
条件类型允许我们在类型层面进行条件判断,这是 TypeScript 类型系统的强大功能。
typescript
// 基本条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 更实用的例子
type MessageOf<T> = T extends { message: unknown } ? T['message'] : never;
interface Email {
message: string;
}
interface Dog {
bark(): void;
}
type EmailMessageContents = MessageOf<Email>; // string
type DogMessageContents = MessageOf<Dog>; // never
2.2 分配条件类型
当条件类型的检查类型是裸类型参数(naked type parameter)时,它会在联合类型上进行分配。
typescript
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
// 如果想阻止这种行为,可以用方括号包裹类型
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type StrArrOrNumArr2 = ToArrayNonDist<string | number>; // (string | number)[]
2.3 infer 关键字
infer 关键字允许我们在条件类型中推断类型变量。
typescript
// 推断函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type Func = () => number;
type Result = ReturnType<Func>; // number
// 推断数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : T;
type Arr = ElementType<string[]>; // string
type NotArr = ElementType<string>; // string
// 推断 Promise 的值类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type PromiseValue = UnwrapPromise<Promise<string>>; // string
2.4 预定义的条件类型
TypeScript 内置了一些有用的条件类型:
typescript
// Exclude<UnionType, ExcludedMembers> - 从联合类型中排除某些成员
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
// Extract<Type, Union> - 从联合类型中提取某些成员
type T3 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T4 = Extract<string | number | boolean, boolean | number>; // number | boolean
// NonNullable<Type> - 从类型中排除 null 和 undefined
type T5 = NonNullable<string | number | undefined>; // string | number
type T6 = NonNullable<string[] | null | undefined>; // string[]
// Parameters<Type> - 获取函数参数类型
type T7 = Parameters<() => string>; // []
type T8 = Parameters<(s: string) => void>; // [string]
// ConstructorParameters<Type> - 获取构造函数参数类型
class Person {
constructor(public name: string, public age: number) {}
}
type T9 = ConstructorParameters<typeof Person>; // [string, number]
// InstanceType<Type> - 获取构造函数实例类型
type T10 = InstanceType<typeof Person>; // Person
// Required<Type> - 将所有属性设为必需
interface Props {
a?: number;
b?: string;
}
type T11 = Required<Props>; // { a: number; b: string; }
// Partial<Type> - 将所有属性设为可选
type T12 = Partial<Props>; // { a?: number; b?: string; }
// Readonly<Type> - 将所有属性设为只读
type T13 = Readonly<Props>; // { readonly a?: number; readonly b?: string; }
// Pick<Type, Keys> - 从类型中选择特定属性
type T14 = Pick<Props, "a">; // { a?: number; }
// Omit<Type, Keys> - 从类型中省略特定属性
type T15 = Omit<Props, "a">; // { b?: string; }
// Record<Keys, Type> - 创建属性为 Keys,值为 Type 的对象类型
type T16 = Record<'x' | 'y', number>; // { x: number; y: number; }
2.5 映射类型
映射类型是一种从旧类型创建新类型的泛型类型,它使用 PropertyKey 的联合来遍历键以创建类型:
typescript
// 基本映射类型
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<FeatureFlags>;
// {
// darkMode: boolean;
// newUserProfile: boolean;
// }
// 使用 readonly 和 ? 修饰符
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
// { id: string; name: string; }
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
// { id: string; name: string; age: number; }
2.6 Key Remapping via as
TypeScript 4.1 引入了 key remapping,允许我们在映射类型中转换键名:
typescript
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
// 通过模板字面量类型过滤键
type RemoveKindField<Type> = {
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
// { radius: number; }
2.7 深层映射类型
创建深层映射类型来处理嵌套对象:
typescript
// 深度 Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type NestedObject = {
a: {
b: {
c: number;
};
d: string;
};
e: boolean;
};
type PartialNested = DeepPartial<NestedObject>;
// {
// a?: {
// b?: {
// c?: number;
// };
// d?: string;
// };
// e?: boolean;
// }
// 深度 Required
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
三、装饰器模式(Decorator Pattern)
3.1 装饰器基础
装饰器是一种特殊类型的声明,可以被附加到类声明、方法、访问符、属性或参数上。装饰器使用 @expression 形式,expression 求值后必须为一个函数,它会在运行时被调用。
注意:装饰器目前还是实验性特性,需要在 tsconfig.json 中启用:
json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
3.2 类装饰器
类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。
typescript
// 类装饰器函数
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
// 替换类构造函数的装饰器
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}
@classDecorator
class Greeter2 {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter2("world"));
3.3 方法装饰器
方法装饰器应用于方法的属性描述符,可以用来监视、修改或替换方法定义。
typescript
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Greeter3 {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
3.4 访问器装饰器
访问器装饰器应用于访问器的属性描述符,可以用来监视、修改或替换访问器的定义。
typescript
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
@configurable(false)
get y() { return this._y; }
}
3.5 属性装饰器
属性装饰器应用于类的属性声明,不能用来改变一个属性的声明或实现,但可以用来监视一个属性在类中的访问。
typescript
function format(formatString: string) {
return function (target: any, propertyKey: string) {
// 存储格式信息,可以在其他地方使用
let value: string;
const descriptor: PropertyDescriptor = {
get: function () {
return value;
},
set: function (newVal) {
value = formatString.replace('{0}', newVal);
},
enumerable: true,
configurable: true
};
Object.defineProperty(target, propertyKey, descriptor);
}
}
class Greeter4 {
@format("Hello, {0}")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
const greeter = new Greeter4("World");
console.log(greeter.greeting); // "Hello, World"
3.6 参数装饰器
参数装饰器应用于类构造函数或方法的参数,参数装饰器只能用来监视一个方法的参数是否被传入。
typescript
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
// 可以记录哪些参数是必需的
console.log(`Parameter ${parameterIndex} is required for method ${propertyKey.toString()}`);
}
class Greeter5 {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
3.7 装饰器工厂
装饰器工厂是可以传参的装饰器,返回一个装饰器函数。
typescript
function log(level: string) {
return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`[${level}] Calling ${propertyName} with`, args);
const result = method.apply(this, args);
console.log(`[${level}] Method ${propertyName} returned`, result);
return result;
}
}
}
class Calculator {
@log('DEBUG')
add(a: number, b: number) {
return a + b;
}
@log('INFO')
multiply(a: number, b: number) {
return a * b;
}
}
const calc = new Calculator();
calc.add(2, 3); // [DEBUG] Calling add with [2, 3]
// [DEBUG] Method add returned 5
3.8 复合装饰器
多个装饰器可以应用在一个声明上,它们会按照特定顺序求值和应用。
typescript
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
}
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
}
}
class Example {
@first()
@second()
method() {}
}
// 输出顺序:
// first(): factory evaluated
// second(): factory evaluated
// second(): called
// first(): called
四、模块声明和类型增强
4.1 模块声明基础
TypeScript 允许你为现有的 JavaScript 库编写声明文件,以便在 TypeScript 项目中获得类型安全。
typescript
// 声明全局变量
declare var $: JQueryStatic;
// 声明全局函数
declare function greet(name: string): void;
// 声明全局对象
declare namespace MyApp {
interface Config {
apiUrl: string;
timeout: number;
}
function init(config: Config): void;
}
4.2 模块声明
当你需要为外部库提供类型信息时,可以创建声明文件:
typescript
// myLibrary.d.ts
declare module "myLibrary" {
export interface Options {
timeout?: number;
retries?: number;
}
export function request(url: string, options?: Options): Promise<any>;
export function cancel(token: any): void;
}
// 使用
import { request, Options } from "myLibrary";
const options: Options = {
timeout: 5000,
retries: 3
};
request("https://api.example.com/data", options);
4.3 通配符模块声明
当你需要为一组模块提供声明时,可以使用通配符:
typescript
// 为所有 .css 文件提供声明
declare module "*.css" {
const content: { [className: string]: string };
export default content;
}
// 为所有图片文件提供声明
declare module "*.png" {
const value: string;
export default value;
}
declare module "*.jpg" {
const value: string;
export default value;
}
declare module "*.svg" {
const value: string;
export default value;
}
4.4 类型增强(Augmenting)
TypeScript 允许你扩展现有类型,添加新的属性或方法。
typescript
// 扩展全局 Window 接口
declare global {
interface Window {
myCustomProperty: string;
myCustomFunction(): void;
}
}
// 扩展已有接口
interface Array<T> {
groupBy<U>(keySelector: (item: T) => U): Map<U, T[]>;
}
// 实现扩展的方法
Array.prototype.groupBy = function<T, U>(keySelector: (item: T) => U): Map<U, T[]> {
const map = new Map<U, T[]>();
this.forEach(item => {
const key = keySelector(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
});
return map;
};
4.5 模块增强(Module Augmentation)
你可以扩展现有模块的类型:
typescript
// observable.d.ts
export declare class Observable<T> {
subscribe(observer: (value: T) => void): void;
}
// 扩展 Observable 模块
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<R>(project: (value: T) => R): Observable<R>;
filter(predicate: (value: T) => boolean): Observable<T>;
}
}
// 实现扩展的方法
Observable.prototype.map = function(project) {
// 实现 map 方法
return new Observable(/* ... */);
};
Observable.prototype.filter = function(predicate) {
// 实现 filter 方法
return new Observable(/* ... */);
};
4.6 声明合并(Declaration Merging)
TypeScript 允许将多个同名声明合并为一个声明:
typescript
// 接口合并
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
// 命名空间合并
namespace Animals {
export class Zebra { }
}
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
// 合并后的 Animals 包含 Zebra, Legged, Dog
五、类型推导和类型守卫
5.1 类型推导基础
TypeScript 编译器会根据赋值表达式和其他上下文自动推断类型。
typescript
// 基础类型推导
let x = 3; // 推导为 number
let y = [0, 1, null]; // 推导为 (number | null)[]
// 最佳通用类型
let zoo = [new Rhino(), new Elephant(), new Snake()];
// 推导为 (Rhino | Elephant | Snake)[]
// 上下文类型推导
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); // 正确,推导为 MouseEvent
// console.log(mouseEvent.foo); // 错误,MouseEvent 没有 foo 属性
};
5.2 控制流类型分析
TypeScript 3.7 引入了更精确的控制流类型分析:
typescript
function example(x: string | number | boolean) {
if (typeof x === "string") {
x; // 推导为 string
x.toUpperCase();
} else if (typeof x === "number") {
x; // 推导为 number
x.toFixed();
} else {
x; // 推导为 boolean
x.valueOf();
}
}
// 数组类型守卫
function isStringArray(arr: unknown[]): arr is string[] {
return arr.every(item => typeof item === "string");
}
function processArray(arr: (string | number)[]) {
if (isStringArray(arr)) {
arr; // 推导为 string[]
arr.map(str => str.toUpperCase());
} else {
// 处理混合数组
}
}
5.3 类型守卫(Type Guards)
类型守卫是一些表达式,它们在运行时检查以确保在某个作用域内的类型。
typescript
// typeof 类型守卫
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return " ".repeat(padding) + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
// instanceof 类型守卫
class Bird {
fly() {}
layEggs() {}
}
class Fish {
swim() {}
layEggs() {}
}
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
animal.fly();
} else {
animal.swim();
}
}
// in 操作符类型守卫
interface Car {
engine: string;
}
interface Bicycle {
pedals: number;
}
function inspectVehicle(vehicle: Car | Bicycle) {
if ("engine" in vehicle) {
vehicle; // 推导为 Car
console.log(vehicle.engine);
} else {
vehicle; // 推导为 Bicycle
console.log(vehicle.pedals);
}
}
5.4 自定义类型守卫
你可以创建自己的类型守卫函数来细化类型:
typescript
// 用户定义的类型守卫
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function getSmallPet(): Fish | Bird {
// 返回 Fish 或 Bird 的实现
return Math.random() > 0.5 ? new Fish() : new Bird();
}
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
// 更复杂的类型守卫
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function isAdmin(employee: UnknownEmployee): employee is Admin {
return "privileges" in employee;
}
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if (isAdmin(emp)) {
console.log("Privileges: " + emp.privileges.join(", "));
} else {
console.log("Start Date: " + emp.startDate);
}
}
5.5 可辨识联合(Discriminated Unions)
通过共同的可辨识字段来区分不同类型的联合类型:
typescript
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2;
default:
// 穷尽性检查
const _exhaustiveCheck: never = s;
return _exhaustiveCheck;
}
}
// 添加新形状时,编译器会报错,提醒你处理新情况
interface Triangle {
kind: "triangle";
base: number;
height: number;
}
// type Shape = Square | Rectangle | Circle | Triangle;
// 上述代码会导致编译错误,因为 area 函数没有处理 triangle 情况
5.6 类型谓词(Type Predicates)
类型谓词是返回类型为 type is TypeName 的函数,用于类型守卫:
typescript
// 数组类型谓词
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string");
}
// 对象类型谓词
interface User {
id: number;
name: string;
email: string;
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === "object" &&
obj !== null &&
"id" in obj &&
"name" in obj &&
"email" in obj &&
typeof (obj as User).id === "number" &&
typeof (obj as User).name === "string" &&
typeof (obj as User).email === "string"
);
}
// 使用类型谓词
function processData(data: unknown) {
if (isStringArray(data)) {
// data 被推导为 string[]
data.map(str => str.toUpperCase());
} else if (isUser(data)) {
// data 被推导为 User
console.log(`User: ${data.name} (${data.email})`);
} else {
console.log("Unknown data type");
}
}
5.7 类型断言(Type Assertions)
虽然不是类型推导的一部分,但类型断言是 TypeScript 中重要的类型操作:
typescript
// 尖括号语法
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
// as 语法(推荐)
let someValue2: unknown = "this is a string";
let strLength2: number = (someValue2 as string).length;
// 非空断言操作符
function fixed(name: string | null): string {
function postprocess(name: string) {
return name.charAt(0).toUpperCase() + name.substr(1);
}
// name 可能为 null,但我们确定它不为 null
return postprocess(name!);
}
// 类型断言 vs 类型守卫
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function handleAnimal(animal: Cat | Dog) {
// 类型断言(不安全)
(animal as Cat).meow(); // 如果 animal 是 Dog,会运行时报错
// 类型守卫(安全)
if ("meow" in animal) {
animal.meow(); // 安全调用
}
}
总结
TypeScript 的高级特性为我们提供了强大的类型系统和编程能力:
- 泛型编程让我们能够编写高度可重用且类型安全的代码
- 条件类型和映射类型提供了强大的类型变换能力
- 装饰器模式允许我们以声明式的方式增强类和成员的行为
- 模块声明和类型增强使我们能够为现有库添加类型信息并扩展类型
- 类型推导和类型守卫让 TypeScript 能够在运行时保持类型安全
掌握这些高级特性不仅能提高代码质量和开发效率,还能让我们写出更加健壮和易于维护的应用程序。随着 TypeScript 生态的不断发展,这些特性将在现代前端开发中发挥越来越重要的作用。