跳到主要内容

TypeScript 高级类型

TypeScript 提供了强大的类型操作能力,可以创建灵活且类型安全的代码。本章将深入探讨高级类型技巧,包括类型体操、条件类型、映射类型等核心概念。

satisfies 操作符

satisfies 操作符是 TypeScript 4.9 引入的新特性,它允许我们验证表达式的类型是否符合某个约束,同时保留表达式的原始类型信息。这解决了传统类型注解会"丢失"具体类型信息的问题。

为什么需要 satisfies

考虑这样一个场景:我们定义一个颜色配置对象,希望确保每个属性都是字符串或数字数组,但同时需要保留每个属性的具体类型。

// 使用类型注解 - 丢失了具体类型信息
type Colors = Record<string, string | number[]>;

const palette: Colors = {
red: "#FF0000",
blue: [0, 0, 255],
green: "#00FF00"
};

// 问题:TypeScript 不知道 palette.red 是 string
const red: string = palette.red; // OK,但 palette.red 的类型是 string | number[]
// palette.red.toUpperCase(); // 错误:number[] 没有 toUpperCase 方法

// 需要类型断言才能使用
(palette.red as string).toUpperCase(); // OK,但不优雅

问题分析:类型注解虽然确保了类型安全,但导致 palette.red 的类型变成了 string | number[],丢失了它实际是 string 的信息。

satisfies 的解决方案

satisfies 可以验证类型约束,同时保留原始的具体类型:

type Colors = Record<string, string | number[]>;

const palette = {
red: "#FF0000",
blue: [0, 0, 255],
green: "#00FF00"
} satisfies Colors;

// 满足类型约束,同时保留了具体类型
palette.red.toUpperCase(); // OK!palette.red 的类型是 string
palette.blue.push(128); // OK!palette.blue 的类型是 number[]
// palette.green.toFixed(); // 错误:string 没有 toFixed 方法

解释satisfies 做了两件事:

  1. 验证对象是否符合 Colors 类型的约束
  2. 保留对象属性的具体类型(red: stringblue: number[]

satisfies vs 类型注解 vs 类型断言

interface Config {
host: string;
port: number;
debug?: boolean;
}

// 方式一:类型注解 - 类型变成 Config,丢失具体信息
const config1: Config = {
host: "localhost",
port: 3000
};
// config1.port 的类型是 number

// 方式二:satisfies - 验证约束,保留具体类型
const config2 = {
host: "localhost",
port: 3000 as const
} satisfies Config;
// config2.port 的类型是 3000(字面量类型)

// 方式三:类型断言 - 不验证,直接断言
const config3 = {
host: "localhost",
port: 3000
} as Config;
// 不安全,如果属性不匹配不会有编译错误
方式类型验证保留具体类型安全性
类型注解 : Type
satisfies Type
类型断言 as Type

实际应用场景

场景一:确保对象拥有所有必需的键

type Routes = {
home: string;
about: string;
contact: string;
};

// 使用 satisfies 确保所有键都存在
const routes = {
home: "/",
about: "/about",
contact: "/contact"
} satisfies Routes;

// 如果缺少某个键,编译时会报错
const invalidRoutes = {
home: "/",
about: "/about"
// 错误:缺少 contact 属性
} satisfies Routes;

场景二:配置对象验证

interface CompilerOptions {
target?: string;
strict?: boolean;
outDir?: string;
extends?: string | string[];
}

const myConfig = {
target: "ES2020",
strict: true,
outDir: "./dist",
extends: ["@tsconfig/strictest", "./tsconfig.base.json"]
} satisfies CompilerOptions;

// 可以安全地使用 extends,因为它可能是 string[]
myConfig.extends.forEach(ext => console.log(ext)); // OK

场景三:确保属性类型正确

type EventHandlers = {
[K: string]: (event: Event) => void;
};

const handlers = {
click: (e: MouseEvent) => console.log("clicked", e.clientX),
focus: (e: FocusEvent) => console.log("focused"),
keydown: (e: KeyboardEvent) => console.log("key", e.key)
} satisfies EventHandlers;

// handlers.click 的参数类型被正确推断为 MouseEvent
// 如果参数类型不匹配,会报错
const badHandlers = {
click: (s: string) => console.log(s) // 错误:参数类型不匹配
} satisfies EventHandlers;

场景四:结合 as const 使用

type Status = "pending" | "approved" | "rejected";

const statusLabels = {
pending: "等待审核",
approved: "已通过",
rejected: "已拒绝"
} as const satisfies Record<Status, string>;

// statusLabels.pending 的类型是 "等待审核"(字面量类型)
// 同时确保所有 Status 都有对应的标签

satisfies 的限制

// satisfies 只能用于表达式,不能用于声明
let x: string = "hello" satisfies string; // OK

// 不能用于函数参数声明
function foo(x: string satisfies string) {} // 错误

// 不能用于类属性声明
class Example {
name: string satisfies string; // 错误
}

在 JSDoc 中使用 satisfies

TypeScript 5.0 开始支持在 JSDoc 中使用 @satisfies 标签:

/**
* @type {Record<string, string | number[]>}
*/
const palette = {
red: "#FF0000",
blue: [0, 0, 255]
};

// 使用 @satisfies 保留类型信息
/**
* @satisfies {Record<string, string | number[]>}
*/
const paletteWithSatisfies = {
red: "#FF0000",
blue: [0, 0, 255]
};

最佳实践

  1. 优先使用 satisfies 而非类型断言satisfies 会进行类型检查,更安全
  2. 结合 as const 使用:可以获得更精确的字面量类型
  3. 用于配置验证:确保配置对象符合预期结构,同时保留具体类型
// 推荐写法
const config = {
api: {
baseUrl: "https://api.example.com",
timeout: 5000
},
features: {
darkMode: true,
notifications: false
}
} as const satisfies AppConfig;

联合类型与交叉类型

联合类型

联合类型表示一个值可以是多种类型之一,使用 | 操作符连接:

// 基本联合类型
type ID = string | number;

let userId: ID;
userId = "abc123"; // OK
userId = 123; // OK
// userId = true; // 错误:不能将 boolean 分配给 string | number

// 字符串字面量联合类型
type Status = "pending" | "approved" | "rejected";
type Role = "admin" | "user" | "guest";

function updateStatus(status: Status): void {
console.log(`状态更新为: ${status}`);
}

updateStatus("approved"); // OK
// updateStatus("unknown"); // 错误:不在联合类型中

交叉类型

交叉类型将多个类型合并为一个类型,使用 & 操作符连接:

interface Name {
name: string;
}

interface Age {
age: number;
}

type Person = Name & Age;

const person: Person = {
name: "张三",
age: 25
};

// 合并多个类型
interface Employee {
employeeId: string;
}

interface Manager {
teamSize: number;
}

type ManagerEmployee = Employee & Manager & Person;

const manager: ManagerEmployee = {
name: "李四",
age: 30,
employeeId: "E001",
teamSize: 5
};

合并冲突处理

当两个类型有同名但类型不同的属性时,交叉类型会变为 never

interface A {
name: string;
value: number;
}

interface B {
name: string;
value: string; // 与 A 冲突
}

type C = A & B;

// value 类型变为 never,因为 number & string 没有交集
const c: C = {
name: "test",
// value: ??? // 无法赋任何值
};

解决方案:使用联合类型或 Omit 排除冲突属性:

// 方案1:使用联合类型
type D = A | B;

// 方案2:排除冲突属性
type E = A & Omit<B, 'value'>;

类型收窄

TypeScript 会在条件分支中自动收窄联合类型的范围:

function process(value: string | number): string {
// typeof 类型守卫
if (typeof value === "string") {
// 这里 value 被收窄为 string 类型
return value.toUpperCase();
} else {
// 这里 value 被收窄为 number 类型
return value.toFixed(2);
}
}

// 数组方法中的类型收窄
function getFirst(value: string | string[] | null): string {
if (value === null) {
return "empty";
}
if (Array.isArray(value)) {
return value[0] || "empty";
}
return value;
}

// in 操作符收窄
interface Bird {
fly(): void;
layEggs(): void;
}

interface Fish {
swim(): void;
layEggs(): void;
}

function move(animal: Bird | Fish): void {
if ("fly" in animal) {
animal.fly(); // animal 被收窄为 Bird
} else {
animal.swim(); // animal 被收窄为 Fish
}
}

类型守卫

自定义类型守卫

使用 is 关键字创建类型谓词函数:

interface User {
name: string;
email: string;
}

interface Admin extends User {
permissions: string[];
}

// 类型守卫函数
function isAdmin(user: User | Admin): user is Admin {
return "permissions" in user;
}

function checkAccess(user: User | Admin): void {
if (isAdmin(user)) {
// user 这里被收窄为 Admin 类型
console.log(`管理员,拥有 ${user.permissions.length} 个权限`);
} else {
// user 这里被收窄为 User 类型
console.log(`普通用户: ${user.name}`);
}
}

类型谓词的工作原理user is Admin 告诉 TypeScript,当函数返回 true 时,参数的类型可以被视为 Admin

断言函数

使用 asserts 关键字创建断言函数:

function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Value is not a string");
}
}

function processValue(value: unknown) {
assertIsString(value);
// 这里 value 被收窄为 string 类型
console.log(value.toUpperCase());
}

可辨识联合

使用共同属性来区分联合类型成员:

interface Circle {
kind: "circle";
radius: number;
}

interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}

interface Triangle {
kind: "triangle";
base: number;
height: number;
}

type Shape = Circle | Rectangle | Triangle;

function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
}
}

// 穷尽检查
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}

function getAreaSafe(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// 如果新增了 Shape 类型但忘记处理,这里会报错
return assertNever(shape);
}
}

条件类型

基本语法

条件类型类似于 JavaScript 的三元表达式:

type IsString<T> = T extends string ? true : false;

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

分布式条件类型

当条件类型作用于联合类型时,会自动分布到每个成员:

type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// 等价于 ToArray<string> | ToArray<number>
// 结果为 string[] | number[]

避免分布:使用元组包裹类型:

type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type Result = ToArrayNonDist<string | number>;
// 结果为 (string | number)[]

infer 关键字

infer 用于在条件类型中推断类型变量:

// 提取函数返回类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet(): string {
return "hello";
}

type GreetReturn = MyReturnType<typeof greet>; // string

// 提取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

type Params = MyParameters<(a: string, b: number) => void>;
// [string, number]

// 提取 Promise 值类型
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;

type P = MyAwaited<Promise<Promise<string>>>; // string(递归解包)

多个 infer 推断

// 提取对象的属性名和属性值类型
type ObjectEntries<T> = T extends object
? { [K in keyof T]: [K, T[K]] }[keyof T]
: never;

type Entries = ObjectEntries<{ a: string; b: number }>;
// ["a", string] | ["b", number]

// 提取元组的第一个和剩余元素
type Head<T> = T extends [infer H, ...any[]] ? H : never;
type Tail<T> = T extends [any, ...infer R] ? R : never;

type H = Head<[1, 2, 3]>; // 1
type T = Tail<[1, 2, 3]>; // [2, 3]

// 提取数组的最后一个元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type L = Last<[1, 2, 3]>; // 3

实用条件类型

// 检查类型是否为 never
type IsNever<T> = [T] extends [never] ? true : false;

// 检查类型是否为 any
type IsAny<T> = 0 extends (1 & T) ? true : false;

// 检查类型是否为数组
type IsArray<T> = T extends any[] ? true : false;

// 检查两个类型是否相等
type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;

// 排除 undefined 和 null
type NonNullable<T> = T extends null | undefined ? never : T;

// 提取联合类型的成员
type Extract<T, U> = T extends U ? T : never;
type ExtractStrings = Extract<string | number | boolean, string>; // string

// 排除联合类型的成员
type Exclude<T, U> = T extends U ? never : T;
type ExcludeStrings = Exclude<string | number | boolean, string>; // number | boolean

映射类型

基本映射类型

// 将所有属性变为只读
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};

// 将所有属性变为可选
type MyPartial<T> = {
[P in keyof T]?: T[P];
};

// 将所有属性变为必选
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};

// 选取部分属性
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};

// 排除部分属性
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

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

type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }

type UserWithoutEmail = Omit<User, "email">;
// { id: number; name: string; age: number; }

映射修饰符

// 添加修饰符:+readonly, +?
type AddModifiers<T> = {
+readonly [P in keyof T]?: T[P];
};

// 移除修饰符:-readonly, -?
type RemoveModifiers<T> = {
-readonly [P in keyof T]-?: T[P];
};

键重映射(Key Remapping)

使用 as 子句重新映射键名:

// 重命名属性
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person {
name: string;
age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

// 过滤属性
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};

type StringProps = OnlyStrings<{ a: string; b: number; c: string }>;
// { a: string; c: string; }

// 排除特定属性
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K];
};

type NoFunctions = OmitByType<{ a: string; b: () => void; c: number }, Function>;
// { a: string; c: number; }

深层映射类型

递归地应用映射类型:

// 深层 Partial
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;

// 深层 Required
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;

// 深层 Readonly
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;

interface Nested {
user: {
profile: {
name: string;
age?: number;
};
};
}

type PartialNested = DeepPartial<Nested>;
// 所有层级都变为可选

type RequiredNested = DeepRequired<Nested>;
// 所有层级都变为必选

模板字面量类型

基本用法

type Greeting = `hello ${string}`;

let message: Greeting = "hello world"; // OK
let message2: Greeting = "hello there"; // OK
// let message3: Greeting = "hi world"; // 错误:不匹配模板

// 结合联合类型生成所有组合
type Color = "red" | "blue" | "green";
type Size = "small" | "medium" | "large";

type ColorSize = `${Color}-${Size}`;
// "red-small" | "red-medium" | "red-large" | "blue-small" | ...(共9种)

内置字符串工具类型

TypeScript 提供了四种内置的字符串操作类型:

// Uppercase - 转大写
type Upper = Uppercase<"hello">; // "HELLO"

// Lowercase - 转小写
type Lower = Lowercase<"HELLO">; // "hello"

// Capitalize - 首字母大写
type Cap = Capitalize<"hello">; // "Hello"

// Uncapitalize - 首字母小写
type Uncap = Uncapitalize<"Hello">; // "hello"

实际应用场景

// 生成事件处理器类型
type EventHandler<T extends string> = `on${Capitalize<T>}`;

type MouseEvents = "click" | "mousedown" | "mouseup";
type Handlers = EventHandler<MouseEvents>;
// "onClick" | "onMousedown" | "onMouseup"

// 生成 getter/setter 类型
type GetterName<K extends string> = `get${Capitalize<K>}`;
type SetterName<K extends string> = `set${Capitalize<K>}`;

type Getters<T> = {
[K in keyof T as GetterName<string & K>]: () => T[K];
};

type Setters<T> = {
[K in keyof T as SetterName<string & K>]: (value: T[K]) => void;
};

interface State {
count: number;
name: string;
}

type StateGetters = Getters<State>;
// { getCount: () => number; getName: () => string; }

type StateSetters = Setters<State>;
// { setCount: (value: number) => void; setName: (value: string) => void; }

// 解析路径参数
type Split<S extends string, D extends string> =
S extends `${infer T}${D}${infer U}`
? [T, ...Split<U, D>]
: [S];

type Path = Split<"a/b/c", "/">; // ["a", "b", "c"]

索引访问类型

属性访问

使用 [] 访问类型的属性:

interface User {
name: string;
age: number;
address: {
city: string;
country: string;
};
}

type UserName = User["name"]; // string
type UserAge = User["age"]; // number
type UserAddress = User["address"]; // { city: string; country: string; }
type UserCity = User["address"]["city"]; // string

// 动态属性访问
type UserKeys = keyof User; // "name" | "age" | "address"

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

数组元素类型

// 获取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;
type Item = ElementType<string[]>; // string

// 使用索引访问
type Element = string[][number]; // string

// const 断言获取字面量联合类型
const colors = ["red", "green", "blue"] as const;

type Color = typeof colors[number]; // "red" | "green" | "blue"
type ColorTuple = typeof colors; // readonly ["red", "green", "blue"]

类型体操实战

常用工具类型

// 1. 将对象的所有函数属性提取出来
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];

type Functions = FunctionKeys<{ a: string; b: () => void; c: number; d: () => string }>;
// "b" | "d"

// 2. 将对象的所有非函数属性提取出来
type NonFunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K
}[keyof T];

// 3. 合并两个对象类型(后者覆盖前者)
type Merge<A, B> = Omit<A, keyof B> & B;

// 4. 将只读属性变为可写
type Writable<T> = {
-readonly [K in keyof T]: T[K];
};

// 5. 必选某些属性
type RequiredKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;

// 6. 可选某些属性
type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

// 7. 将某个键变为必需
type RequireAtLeastOne<T, K extends keyof T> = T & { [P in K]-?: T[P] };

递归类型

// 数组扁平化类型
type Flatten<T> = T extends (infer E)[]
? Flatten<E>
: T;

type Flat = Flatten<[1, [2, [3, [4]]]]>; // number

// 对象路径类型
type Path<T, K extends keyof T = keyof T> =
K extends string | number
? T[K] extends object
? K | `${K}.${Path<T[K]>}`
: K
: never;

type ObjPath = Path<{ a: { b: { c: string }; d: number }; e: string }>;
// "a" | "a.b" | "a.b.c" | "a.d" | "e"

// 根据路径获取类型
type PathValue<T, P extends string> =
P extends `${infer K}.${infer R}`
? K extends keyof T
? PathValue<T[K], R>
: never
: P extends keyof T
? T[P]
: never;

type Value = PathValue<{ a: { b: string } }, "a.b">; // string

元组操作

// 反转元组
type Reverse<T extends any[]> = T extends [infer First, ...infer Rest]
? [...Reverse<Rest>, First]
: [];

type Reversed = Reverse<[1, 2, 3]>; // [3, 2, 1]

// 过滤元组元素
type Filter<T extends any[], U> = T extends [infer First, ...infer Rest]
? First extends U
? [First, ...Filter<Rest, U>]
: Filter<Rest, U>
: [];

type Filtered = Filter<[1, "a", 2, "b", 3], number>; // [1, 2, 3]

// 元组长度
type Length<T extends any[]> = T["length"];
type Len = Length<[1, 2, 3]>; // 3

// 连接元组
type Concat<T extends any[], U extends any[]> = [...T, ...U];
type Concated = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]

函数类型操作

// 提取构造函数参数类型
type ConstructorParameters<T extends new (...args: any[]) => any> =
T extends new (...args: infer P) => any ? P : never;

class Example {
constructor(a: string, b: number) {}
}

type Params = ConstructorParameters<typeof Example>; // [string, number]

// 提取实例类型
type InstanceType<T extends new (...args: any[]) => any> =
T extends new (...args: any[]) => infer R ? R : never;

type Instance = InstanceType<typeof Example>; // Example

// 提取 this 参数类型
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

// 移除 this 参数
type OmitThisParameter<T> = T extends (this: any, ...args: infer A) => infer R
? (...args: A) => R
: T;

类型挑战示例

// 1. 实现 Includes
type Includes<T extends any[], U> = T extends [infer First, ...infer Rest]
? Equal<First, U> extends true
? true
: Includes<Rest, U>
: false;

type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)
? true
: false;

// 2. 实现 Push
type Push<T extends any[], U> = [...T, U];
type Pushed = Push<[1, 2], 3>; // [1, 2, 3]

// 3. 实现 Pop
type Pop<T extends any[]> = T extends [...infer Rest, infer _] ? Rest : never;
type Popped = Pop<[1, 2, 3]>; // [1, 2]

// 4. 实现 Unshift
type Unshift<T extends any[], U> = [U, ...T];
type Unshifted = Unshift<[2, 3], 1>; // [1, 2, 3]

// 5. 实现 PromiseAll 返回类型
type PromiseAll<T extends any[]> = Promise<{
[K in keyof T]: T[K] extends Promise<infer U> ? U : T[K];
}>;

// 6. 实现 Trim
type TrimLeft<S extends string> = S extends ` ${infer Rest}`
? TrimLeft<Rest>
: S;
type TrimRight<S extends string> = S extends `${infer Rest} `
? TrimRight<Rest>
: S;
type Trim<S extends string> = TrimLeft<TrimRight<S>>;

type Trimmed = Trim<" hello ">; // "hello"

// 7. 实现 Replace
type Replace<S extends string, From extends string, To extends string> =
From extends ""
? S
: S extends `${infer Before}${From}${infer After}`
? `${Before}${To}${After}`
: S;

type Replaced = Replace<"hello world", "world", "ts">; // "hello ts"

// 8. 实现 AppendArgument
type AppendArgument<Fn extends Function, Arg> = Fn extends (...args: infer Args) => infer R
? (...args: [...Args, Arg]) => R
: never;

类型体操技巧总结

技巧一:模式匹配

使用 infer 提取类型的一部分:

// 提取数组第一个元素
type First<T> = T extends [infer F, ...any[]] ? F : never;

// 提取函数返回值
type Return<T> = T extends (...args: any[]) => infer R ? R : never;

技巧二:递归

处理嵌套结构或不确定深度的类型:

// 深层 Promise 解包
type DeepPromise<T> = T extends Promise<infer U> ? DeepPromise<U> : T;

技巧三:数组长度推断

利用数组长度属性:

// 构建指定长度的数组
type BuildTuple<L extends number, T extends any[] = []> =
T["length"] extends L
? T
: BuildTuple<L, [...T, any]>;

技巧四:联合类型分发

利用条件类型的分发特性:

// 将联合类型转为交叉类型
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer R) => void
? R
: never;

小结

本章我们学习了 TypeScript 高级类型的核心概念:

  1. 联合类型与交叉类型:组合类型的两种方式
  2. 类型守卫:在运行时缩小类型范围
  3. 条件类型:基于类型关系进行条件判断
  4. 映射类型:转换现有类型的属性
  5. 模板字面量类型:操作字符串类型
  6. 索引访问类型:通过索引访问类型属性
  7. 类型体操:高级类型操作技巧

这些高级类型特性让 TypeScript 能够精确地描述复杂的类型关系,在大型项目中提供更好的类型安全和开发体验。

练习

  1. 实现一个 DeepPartial<T> 类型,递归地将所有属性变为可选
  2. 实现一个 UnionToIntersection<U> 类型,将联合类型转为交叉类型
  3. 实现一个 RequiredKeys<T> 类型,获取所有必需属性的键名
  4. 实现一个 Chainable 类型,支持链式调用的类型推断

参考资料