Skip to content

第6章 高级类型

本章定位:深入TypeScript的类型系统,学习泛型约束、条件类型、映射类型、工具类型等高级特性,掌握类型级别的编程能力。


6.1 泛型约束(generics constraints)

Section titled “6.1 泛型约束(generics constraints)”

使用extends限制类型参数的范围:

// 要求T必须有length属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): number {
return arg.length;
}
logLength("hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
logLength(123); // 编译错误!number没有length
// T必须是string或number
function identity<T extends string | number>(arg: T): T {
return arg;
}
identity("hello"); // OK
identity(42); // OK
identity(true); // 编译错误!
// 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"); // string
const age = getProperty(user, "age"); // number
const email = getProperty(user, "email"); // string
getProperty(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();
}
// 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
);

条件类型根据输入类型决定输出类型:

// 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"
// 联合类型会被分布式处理
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:提取T中可以赋值给U的类型
type Extract<T, U> = T extends U ? T : never;
type A = Extract<string | number | boolean, string | number>; // string | number
type 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>; // boolean
type E = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type F = Exclude<string, number>; // string

Python对比:

# Python无内建条件类型,但可以用typing模块模拟
from typing import Union, TypeVar
T = TypeVar("T")
U = TypeVar("U")
# 类似Exclude
def exclude(data: list, exclude_values: list) -> list:
return [x for x in data if x not in exclude_values]
# 只能在运行时有这个功能
// 根据属性是否存在决定返回类型
interface User {
name: string;
email: string;
}
interface Guest {
name: string;
}
// 类型T如果有email属性,返回User,否则返回Guest
type 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在条件类型中推断类型:

// 推断函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = ReturnType<() => string>; // string
type B = ReturnType<() => number>; // number
type C = ReturnType<(x: number) => boolean>; // boolean
// 推断数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;
type D = ElementType<string[]>; // string
type 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'>

遍历对象属性并转换:

// 将所有属性转为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 将所有属性转为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 将所有属性转为number
type 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 Optional
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
# Python没有直接对应,需要手写或用库
# 类似Partial的dataclass
@dataclass
class PartialUser:
name: Optional[str] = None
age: Optional[int] = None
email: Optional[str] = None
// 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 }
// 只映射满足条件的属性
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 }
// 深度可选
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?: { ... } }

将所有属性设为可选:

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 });

将所有属性设为必填:

interface User {
name?: string;
age?: number;
email?: string;
}
type RequiredUser = Required<User>;
// { name: string; age: number; email: string }

将所有属性设为只读:

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和email
type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string }

从T中排除指定属性:

interface User {
name: string;
age: number;
email: string;
password: string;
}
// 排除password
type PublicUser = Omit<User, "password">;
// { name: string; age: number; email: string }

创建键值对类型:

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:从T中排除U
type 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>; // string

移除null和undefined:

type T0 = NonNullable<string | null | undefined>; // string
type T1 = NonNullable<number[] | null | undefined>; // number[]
type T2 = NonNullable<"a" | null | undefined>; // "a"

获取函数返回类型:

function createUser(name: string, age: number) {
return { name, age, createdAt: new Date() };
}
type User = ReturnType<typeof createUser>;
// { name: string; age: number; createdAt: Date }

获取函数参数类型为元组:

function fetchData(url: string, options?: { method: string }): Promise<any> {
return fetch(url, options);
}
type FetchParams = Parameters<typeof fetchData>;
// [url: string, options?: { method: string }]

获取构造函数参数类型:

class User {
constructor(
public name: string,
public age: number,
public email?: string
) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number, email?: string]
interface User {
name: string;
age: number;
}
// 从类获取实例类型
class UserClass {
constructor(public name: string, public age: number) {}
}
type Instance = InstanceType<typeof UserClass>;
// UserClass的实例类型

允许任意属性名:

// 字符串索引签名
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"
interface Hybrid {
[key: string]: string | number;
name: string; // 必须是string
age: number; // 必须是number
// 其他字符串属性可以是string或number
}
// 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];
// User
interface ApiResponse {
data: User;
status: number;
}
type ApiDataType = ApiResponse["data"];
// User
// 嵌套访问
interface Nested {
config: {
settings: {
theme: string;
};
};
}
type Theme = Nested["config"]["settings"]["theme"];
// string

6.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"
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"

类型可以引用自身:

// 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"
}
]
};
// 深度可选
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 }
}
};

// 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;
}
// 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;
};

本章深入学习了TypeScript的高级类型系统:

  1. 泛型约束:

    • extends限制类型参数范围
    • keyof获取对象键
    • 多重约束使用&
  2. 条件类型:

    • T extends U ? X : Y语法
    • Extract和Exclude工具
    • infer推断类型
  3. 映射类型:

    • [P in keyof T]遍历属性
    • +?、-?修改可选性
    • as子句重命名键
  4. 工具类型:

    • Partial、Required、Readonly
    • Pick、Omit
    • Record
    • ReturnType、Parameters
  5. 模板字面量类型:

    • 字符串模板语法
    • 内建字符串操作类型
  6. 递归类型:

    • 类型可以引用自身
    • 用于复杂嵌套结构
  7. 接口vs类型别名:

    • 接口适合对象结构、类实现、声明合并
    • 类型别名适合联合、元组、函数、映射

编写getProperty<T, K extends keyof T>函数,从对象获取属性值。

实现Flatten<T>工具类型,将数组类型展平为元素类型。

实现DeepReadonly<T>工具类型,递归将所有属性设为只读。

创建EventHandlers<T>类型,将事件名转换为处理器名。

创建DeepPick<T, P>工具类型,深度选取嵌套属性。



下一章我们将学习TypeScript的模块系统,包括import/export、模块解析、默认导出与命名导出,以及与Python import的对比。