第6章 高级类型
本章定位:深入TypeScript的类型系统,学习泛型约束、条件类型、映射类型、工具类型等高级特性,掌握类型级别的编程能力。
6.1 泛型约束(generics constraints)
Section titled “6.1 泛型约束(generics constraints)”基本泛型约束
Section titled “基本泛型约束”使用extends限制类型参数的范围:
// 要求T必须有length属性interface HasLength { length: number;}
function logLength<T extends HasLength>(arg: T): number { return arg.length;}
logLength("hello"); // 5logLength([1, 2, 3]); // 3logLength({ length: 10 }); // 10logLength(123); // 编译错误!number没有length约束为特定类型
Section titled “约束为特定类型”// T必须是string或numberfunction identity<T extends string | number>(arg: T): T { return arg;}
identity("hello"); // OKidentity(42); // OKidentity(true); // 编译错误!keyof 约束
Section titled “keyof 约束”// K必须是T的属性名function getProperty<T, K extends keyof T>( obj: T, key: K): T[K] { return obj[key];}
const user = { name: "Alice", age: 30, email: "alice@example.com" };
const name = getProperty(user, "name"); // stringconst age = getProperty(user, "age"); // numberconst email = getProperty(user, "email"); // stringgetProperty(user, "phone"); // 编译错误!"phone"不在user中Python对比:
# Python无编译时类型安全约束def get_property(obj: dict, key: str): return obj.get(key)
user = {"name": "Alice", "age": 30}# 运行时不检查key是否有效get_property(user, "phone") # None,不报错// 多个类型参数各自的约束function createPair<T extends object, U extends object>( first: T, second: U): T & U { return { ...first, ...second };}
// 约束T同时满足多个条件interface Printable { print(): void;}
interface Serializable { serialize(): string;}
function process<T extends Printable & Serializable>(obj: T): void { obj.print(); const json = obj.serialize();}泛型约束的实际应用
Section titled “泛型约束的实际应用”// API响应处理interface APIResponse<T> { data: T; status: number; message: string;}
async function fetchData<T>( url: string, validator: (data: unknown) => data is T): Promise<T | null> { const response = await fetch(url); const json = await response.json();
if (validator(json)) { return json; } return null;}
// 使用interface User { name: string; age: number;}
const user = await fetchData<User>( "https://api.example.com/user", (data): data is User => typeof data === "object" && data !== null && "name" in data && "age" in data);6.2 条件类型(conditional types)
Section titled “6.2 条件类型(conditional types)”条件类型根据输入类型决定输出类型:
// T extends U ? X : Y// 如果T是U的子类型,返回X,否则返回Y
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"type B = IsString<number>; // "no"type C = IsString<"hello">; // "yes"分布式条件类型
Section titled “分布式条件类型”// 联合类型会被分布式处理type ToArray<T> = T extends any ? T[] : never;
type A = ToArray<string>; // string[]type B = ToArray<number | string>; // number[] | string[]type C = ToArray<number | string | boolean>; // number[] | string[] | boolean[]Extract 与 Exclude
Section titled “Extract 与 Exclude”// Extract:提取T中可以赋值给U的类型type Extract<T, U> = T extends U ? T : never;
type A = Extract<string | number | boolean, string | number>; // string | numbertype B = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"type C = Extract<string, number>; // never
// Exclude:从T中排除可以赋值给U的类型type Exclude<T, U> = T extends U ? never : T;
type D = Exclude<string | number | boolean, string | number>; // booleantype E = Exclude<"a" | "b" | "c", "a" | "b">; // "c"type F = Exclude<string, number>; // stringPython对比:
# Python无内建条件类型,但可以用typing模块模拟from typing import Union, TypeVar
T = TypeVar("T")U = TypeVar("U")
# 类似Excludedef exclude(data: list, exclude_values: list) -> list: return [x for x in data if x not in exclude_values]
# 只能在运行时有这个功能条件类型的实际应用
Section titled “条件类型的实际应用”// 根据属性是否存在决定返回类型interface User { name: string; email: string;}
interface Guest { name: string;}
// 类型T如果有email属性,返回User,否则返回Guesttype Identify<T> = T extends { email: string } ? User : Guest;
// 使用function identify<T extends { name: string }>(entity: T): Identify<T> { if ("email" in entity) { return entity as Identify<T>; } return { name: entity.name } as Identify<T>;}
const user = identify({ name: "Alice", email: "alice@example.com" });const guest = identify({ name: "Bob" });推断类型(infer)
Section titled “推断类型(infer)”infer在条件类型中推断类型:
// 推断函数返回类型type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = ReturnType<() => string>; // stringtype B = ReturnType<() => number>; // numbertype C = ReturnType<(x: number) => boolean>; // boolean
// 推断数组元素类型type ElementType<T> = T extends (infer E)[] ? E : never;
type D = ElementType<string[]>; // stringtype E = ElementType<number[]>; // number
// 推断构造函数参数type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;
class User { constructor(public name: string, public age: number) {}}
type F = ConstructorParameters<typeof User>; // [string, number]Python对比:
# Python可以用typing模块获取类型信息from typing import get_type_hints, Callable, get_origin
def get_return_type(func: Callable) -> type: hints = get_type_hints(func) return hints.get("return", type(None))
# 运行时才有def foo() -> str: return "hello"
get_return_type(foo) # <class 'str'>6.3 映射类型(mapped types)
Section titled “6.3 映射类型(mapped types)”基本映射类型
Section titled “基本映射类型”遍历对象属性并转换:
// 将所有属性转为可选type Partial<T> = { [P in keyof T]?: T[P];};
// 将所有属性转为只读type Readonly<T> = { readonly [P in keyof T]: T[P];};
// 将所有属性转为numbertype Numberify<T> = { [P in keyof T]: number;};
interface User { name: string; age: number; email: string;}
type PartialUser = Partial<User>;// { name?: string; age?: number; email?: string }
type ReadonlyUser = Readonly<User>;// { readonly name: string; readonly age: number; readonly email: string }Python对比:
from typing import Optionalfrom dataclasses import dataclass
@dataclassclass User: name: str age: int email: str
# Python没有直接对应,需要手写或用库# 类似Partial的dataclass@dataclassclass PartialUser: name: Optional[str] = None age: Optional[int] = None email: Optional[str] = None修改映射修饰符
Section titled “修改映射修饰符”// TypeScript内置的映射修饰符:// -?: 移除可选// +?: 添加可选// -readonly: 移除只读// +readonly: 添加只读
// 移除可选(使属性必填)type Required<T> = { [P in keyof T]-?: T[P];};
// 移除只读type Mutable<T> = { -readonly [P in keyof T]: T[P];};
interface Config { timeout?: number; retries?: number;}
type RequiredConfig = Required<Config>;// { timeout: number; retries: number }
type FrozenConfig = Readonly<Config>;// { readonly timeout?: number; readonly retries?: number }
type UnfrozenConfig = Mutable<FrozenConfig>;// { timeout?: number; retries?: number }// 将键转换为新键名type Getters<T> = { [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];};
interface User { name: string; age: number;}
type UserGetters = Getters<User>;// { getName: () => string; getAge: () => number }条件映射类型
Section titled “条件映射类型”// 只映射满足条件的属性type StringOnly<T> = { [P in keyof T as T[P] extends string ? P : never]: T[P];};
interface Mixed { name: string; age: number; active: boolean;}
type StringsOnly = StringOnly<Mixed>;// { name: string }递归映射类型
Section titled “递归映射类型”// 深度可选type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];};
interface Config { server: { host: string; port: number; }; database: { host: string; name: string; };}
type PartialConfig = DeepPartial<Config>;// { server?: { host?: string; port?: number }; database?: { ... } }6.4 工具类型(Utility Types)
Section titled “6.4 工具类型(Utility Types)”Partial
Section titled “Partial”将所有属性设为可选:
interface User { name: string; age: number; email: string;}
// 所有属性变为可选type PartialUser = Partial<User>;
// 等价于type PartialUserEquivalent = { name?: string; age?: number; email?: string;};
// 实际应用function updateUser(user: User, updates: Partial<User>): User { return { ...user, ...updates };}
const user: User = { name: "Alice", age: 30, email: "alice@example.com" };const updated = updateUser(user, { age: 31 });Required
Section titled “Required”将所有属性设为必填:
interface User { name?: string; age?: number; email?: string;}
type RequiredUser = Required<User>;// { name: string; age: number; email: string }Readonly
Section titled “Readonly”将所有属性设为只读:
interface User { name: string; age: number;}
type FrozenUser = Readonly<User>;
const user: FrozenUser = { name: "Alice", age: 30 };user.name = "Bob"; // 编译错误!readonly从T中选取指定属性:
interface User { name: string; age: number; email: string; password: string;}
// 只选取name和emailtype UserPreview = Pick<User, "name" | "email">;// { name: string; email: string }从T中排除指定属性:
interface User { name: string; age: number; email: string; password: string;}
// 排除passwordtype PublicUser = Omit<User, "password">;// { name: string; age: number; email: string }Record
Section titled “Record”创建键值对类型:
type Role = "admin" | "user" | "guest";type Permission = "read" | "write" | "delete";
// 角色到权限的映射type RolePermissions = Record<Role, Permission[]>;
// 等价于type RolePermissionsEquivalent = { admin: Permission[]; user: Permission[]; guest: Permission[];};
const permissions: RolePermissions = { admin: ["read", "write", "delete"], user: ["read"], guest: []};Exclude与Extract
Section titled “Exclude与Extract”// Exclude:从T中排除Utype T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"type T1 = Exclude<string | number | boolean, string>; // number | boolean
// Extract:提取T中U的部分type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"type T3 = Extract<string | number | boolean, string>; // stringNonNullable
Section titled “NonNullable”移除null和undefined:
type T0 = NonNullable<string | null | undefined>; // stringtype T1 = NonNullable<number[] | null | undefined>; // number[]type T2 = NonNullable<"a" | null | undefined>; // "a"ReturnType
Section titled “ReturnType”获取函数返回类型:
function createUser(name: string, age: number) { return { name, age, createdAt: new Date() };}
type User = ReturnType<typeof createUser>;// { name: string; age: number; createdAt: Date }Parameters
Section titled “Parameters”获取函数参数类型为元组:
function fetchData(url: string, options?: { method: string }): Promise<any> { return fetch(url, options);}
type FetchParams = Parameters<typeof fetchData>;// [url: string, options?: { method: string }]ConstructorParameters
Section titled “ConstructorParameters”获取构造函数参数类型:
class User { constructor( public name: string, public age: number, public email?: string ) {}}
type UserConstructorParams = ConstructorParameters<typeof User>;// [name: string, age: number, email?: string]实例类型工具
Section titled “实例类型工具”interface User { name: string; age: number;}
// 从类获取实例类型class UserClass { constructor(public name: string, public age: number) {}}
type Instance = InstanceType<typeof UserClass>;// UserClass的实例类型6.5 索引类型(index types)
Section titled “6.5 索引类型(index types)”允许任意属性名:
// 字符串索引签名interface StringMap { [key: string]: string;}
const map: StringMap = { name: "Alice", email: "alice@example.com", arbitrary: "value"};
// 数字索引签名interface NumberArray { [index: number]: string;}
const arr: NumberArray = ["a", "b", "c"];console.log(arr[0]); // "a"混合索引签名
Section titled “混合索引签名”interface Hybrid { [key: string]: string | number; name: string; // 必须是string age: number; // 必须是number // 其他字符串属性可以是string或number}索引类型操作
Section titled “索引类型操作”// keyof T:获取所有属性名type Keys = keyof { name: string; age: number };// "name" | "age"
// T[K]:获取属性类型type NameType = { name: string; age: number }["name"];// string
// 数组元素类型type User = { name: string };type Users = User[];type UserElement = Users[number];// Userinterface ApiResponse { data: User; status: number;}
type ApiDataType = ApiResponse["data"];// User
// 嵌套访问interface Nested { config: { settings: { theme: string; }; };}
type Theme = Nested["config"]["settings"]["theme"];// string6.6 模板字面量类型(template literal types)
Section titled “6.6 模板字面量类型(template literal types)”使用模板字符串创建新类型:
type World = "world";type Greeting = `hello, ${World}`;// "hello, world"
// 连接字符串type EventName = "click" | "focus" | "blur";type HandlerName = `on${Capitalize<EventName>}`;// "onClick" | "onFocus" | "onBlur"// 大写type UppercaseName = Uppercase<"alice">; // "ALICE"
// 小写type LowercaseName = Lowercase<"ALICE">; // "alice"
// 首字母大写type CapitalizeName = Capitalize<"alice">; // "Alice"
// 去除首字母大写type UncapitalizeName = Uncapitalize<"Alice">; // "alice"// CSS属性生成type Color = "red" | "green" | "blue";type Direction = "top" | "right" | "bottom" | "left";type CSSProperty = `border-${Direction}` | `background-${Color}`;// "border-top" | "border-right" | ...
// 事件类型type EventType = "click" | "focus" | "blur";type EventHandler = `on${Capitalize<EventType>}`;// "onClick" | "onFocus" | "onBlur"
interface Handlers { onClick: () => void; onFocus: () => void; onBlur: () => void;}
// 函数名构建type Action = "get" | "post" | "put" | "delete";type ApiMethod = `api${Capitalize<Action>}`;// "apiGet" | "apiPost" | "apiPut" | "apiDelete"模板字面量与联合类型
Section titled “模板字面量与联合类型”type Horizontal = "left" | "center" | "right";type Vertical = "top" | "middle" | "bottom";
type Position = `${Horizontal}-${Vertical}`;// "left-top" | "left-middle" | "left-bottom" |// "center-top" | "center-middle" | "center-bottom" |// "right-top" | "right-middle" | "right-bottom"6.7 递归类型
Section titled “6.7 递归类型”类型可以引用自身:
// JSON值的递归类型type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
// 使用const json: JSONValue = { name: "Alice", age: 30, scores: [95, 87, 92], address: { city: "Beijing", zip: "100000" }, active: true, children: null};interface TreeNode<T> { value: T; children?: TreeNode<T>[];}
const tree: TreeNode<string> = { value: "root", children: [ { value: "child1", children: [ { value: "grandchild1" }, { value: "grandchild2" } ] }, { value: "child2" } ]};深度嵌套类型
Section titled “深度嵌套类型”// 深度可选type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
interface Config { server: { host: string; port: number; }; database: { connection: { max: number; timeout: number; }; };}
type PartialConfig = DeepPartial<Config>;// 所有嵌套属性都变成可选// 表达式类型type Expression = | { type: "number"; value: number } | { type: "string"; value: string } | { type: "binary"; operator: "+" | "-"; left: Expression; right: Expression };
const expr: Expression = { type: "binary", operator: "+", left: { type: "number", value: 1 }, right: { type: "binary", operator: "*", left: { type: "number", value: 2 }, right: { type: "number", value: 3 } }};6.8 类别名与接口的选择
Section titled “6.8 类别名与接口的选择”选择接口的场景
Section titled “选择接口的场景”// 1. 需要被类实现interface Serializable { serialize(): string;}
class User implements Serializable { serialize() { return JSON.stringify(this); }}
// 2. 需要声明合并interface Window { title: string;}
interface Window { width: number;}// Window现在有title和width
// 3. 定义对象结构interface Point { x: number; y: number;}选择类型别名的场景
Section titled “选择类型别名的场景”// 1. 联合类型type Result = SuccessResult | ErrorResult;
// 2. 元组类型type Pair = [string, number];
// 3. 函数类型type Callback = (error: Error | null, result?: string) => void;
// 4. 映射类型type Readonly<T> = { readonly [P in keyof T]: T[P];};// 接口定义结构interface User { name: string; age: number;}
// 类型别名定义联合type UserRole = "admin" | "user" | "guest";
// 类型别名扩展接口type AdminUser = User & { role: UserRole; permissions: string[];};
// 接口扩展类型别名(类型别名不能直接扩展,但可以交叉)type ExtendedUser = User & { email: string;};6.9 本章小结
Section titled “6.9 本章小结”本章深入学习了TypeScript的高级类型系统:
-
泛型约束:
extends限制类型参数范围keyof获取对象键- 多重约束使用
&
-
条件类型:
T extends U ? X : Y语法Extract和Exclude工具infer推断类型
-
映射类型:
[P in keyof T]遍历属性+?、-?修改可选性as子句重命名键
-
工具类型:
Partial、Required、ReadonlyPick、OmitRecordReturnType、Parameters
-
模板字面量类型:
- 字符串模板语法
- 内建字符串操作类型
-
递归类型:
- 类型可以引用自身
- 用于复杂嵌套结构
-
接口vs类型别名:
- 接口适合对象结构、类实现、声明合并
- 类型别名适合联合、元组、函数、映射
练习6.1:泛型约束
Section titled “练习6.1:泛型约束”编写getProperty<T, K extends keyof T>函数,从对象获取属性值。
练习6.2:条件类型
Section titled “练习6.2:条件类型”实现Flatten<T>工具类型,将数组类型展平为元素类型。
练习6.3:映射类型
Section titled “练习6.3:映射类型”实现DeepReadonly<T>工具类型,递归将所有属性设为只读。
练习6.4:模板字面量
Section titled “练习6.4:模板字面量”创建EventHandlers<T>类型,将事件名转换为处理器名。
练习6.5:递归类型
Section titled “练习6.5:递归类型”创建DeepPick<T, P>工具类型,深度选取嵌套属性。
下一章我们将学习TypeScript的模块系统,包括import/export、模块解析、默认导出与命名导出,以及与Python import的对比。