====== 第十四章:类型操作符 ======
===== 本章概述 =====
TypeScript 提供了丰富的类型操作符,使我们能够灵活地查询、转换和操作类型。本章将详细介绍 keyof、typeof、in、is 等查询操作符,以及条件类型、映射类型等高级类型操作技术。掌握这些操作符,将使你能够编写出更加灵活和强大的类型定义。
===== 14.1 keyof 操作符 =====
==== 14.1.1 基础用法 ====
keyof 操作符用于获取类型的所有键组成的联合类型。
interface Person {
name: string;
age: number;
email: string;
}
type PersonKeys = keyof Person;
// 等价于: "name" | "age" | "email"
// 使用场景:类型安全的属性访问
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = {
name: "Alice",
age: 25,
email: "alice@example.com"
};
const name = getProperty(person, "name"); // string 类型
const age = getProperty(person, "age"); // number 类型
// const invalid = getProperty(person, "gender"); // Error: "gender" 不是 Person 的键
==== 14.1.2 keyof 与索引签名 ====
// 字符串索引签名
interface StringDictionary {
[key: string]: string;
}
type StringKeys = keyof StringDictionary; // string | number
// 数字索引签名
interface NumberDictionary {
[key: number]: string;
}
type NumberKeys = keyof NumberDictionary; // number
// 结合使用
interface MixedDictionary {
[key: string]: string | number;
length: number; // 必须兼容索引签名
}
type MixedKeys = keyof MixedDictionary; // string | number | "length"
==== 14.1.3 实战应用:pick 函数 ====
// 实现类型安全的对象选择
function pick(obj: T, keys: K[]): Pick {
const result = {} as Pick;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
password: "secret",
createdAt: new Date()
};
// 只选择安全显示的字段
const publicUser = pick(user, ["id", "name", "email"]);
// 类型: { id: number; name: string; email: string }
===== 14.2 typeof 操作符 =====
==== 14.2.1 类型上下文中的 typeof ====
在类型上下文中,typeof 用于获取变量或表达式的类型。
// 获取对象类型
const person = {
name: "Alice",
age: 25,
hobbies: ["reading", "coding"]
};
type PersonType = typeof person;
// {
// name: string;
// age: number;
// hobbies: string[];
// }
// 获取函数类型
function greet(name: string, greeting?: string): string {
return `${greeting ?? "Hello"}, ${name}!`;
}
type GreetFunction = typeof greet;
// (name: string, greeting?: string | undefined) => string
// 获取类实例类型
class User {
constructor(public name: string, public age: number) {}
}
type UserInstance = typeof User.prototype; // User
type UserConstructor = typeof User; // new (name: string, age: number) => User
==== 14.2.2 typeof 与 const 断言结合 ====
// 使用 const 断言获得更精确的类型
const config = {
api: {
endpoint: "/api/v1",
timeout: 5000
},
features: {
darkMode: true,
notifications: false
}
} as const;
type ConfigType = typeof config;
// {
// readonly api: {
// readonly endpoint: "/api/v1";
// readonly timeout: 5000;
// };
// readonly features: {
// readonly darkMode: true;
// readonly notifications: false;
// };
// }
// 提取嵌套类型
type ApiConfig = typeof config.api;
// { readonly endpoint: "/api/v1"; readonly timeout: 5000; }
==== 14.2.3 实战:从运行时对象创建类型 ====
// 运行时配置对象
const API_ENDPOINTS = {
users: "/api/users",
posts: "/api/posts",
comments: "/api/comments"
} as const;
// 从运行时值创建类型
type EndpointPaths = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS];
// "/api/users" | "/api/posts" | "/api/comments"
// 类型安全的 API 客户端
async function fetchApi(
path: EndpointPaths,
options?: RequestInit
): Promise {
const response = await fetch(path, options);
return response.json();
}
// 使用
interface User {
id: number;
name: string;
}
const users = await fetchApi(API_ENDPOINTS.users);
===== 14.3 in 操作符(类型中)=====
==== 14.3.1 映射类型中的 in ====
in 操作符用于遍历联合类型的每个成员,常用于创建映射类型。
// 基础映射类型 - 将所有属性变为只读
type Readonly = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly;
// { readonly name: string; readonly age: number; }
// 将所有属性变为可选
type Partial = {
[P in keyof T]?: T[P];
};
// 将所有属性变为必需
type Required = {
[P in keyof T]-?: T[P]; // -? 移除可选修饰符
};
// 只读数组类型
type ReadonlyArray = {
readonly [P in keyof T[]]: T[P];
};
==== 14.3.2 重映射键名(TypeScript 4.1+)=====
// 使用 as 重映射键名
type Getters = {
[K in keyof T as `get${Capitalize}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters;
// {
// getName: () => string;
// getAge: () => number;
// }
// 过滤特定类型的属性
type StringKeys = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
active: boolean;
}
type OnlyStrings = StringKeys;
// { name: string; email: string; }
==== 14.3.3 实战:创建事件处理器类型 ====
// 根据属性创建对应的事件处理器类型
type EventHandlers = {
[K in keyof T as `on${Capitalize}Change`]?: (
newValue: T[K],
oldValue: T[K]
) => void;
};
interface Settings {
theme: "light" | "dark";
fontSize: number;
notifications: boolean;
}
type SettingsEventHandlers = EventHandlers;
// {
// onThemeChange?: (newValue: "light" | "dark", oldValue: "light" | "dark") => void;
// onFontSizeChange?: (newValue: number, oldValue: number) => void;
// onNotificationsChange?: (newValue: boolean, oldValue: boolean) => void;
// }
// 实现带有事件处理器的类
class ObservableSettings implements Settings {
theme: "light" | "dark" = "light";
fontSize: number = 14;
notifications: boolean = true;
private handlers: EventHandlers = {};
on>(
event: K,
handler: EventHandlers[K]
): void {
this.handlers[event] = handler;
}
set(key: K, value: Settings[K]): void {
const oldValue = this[key];
this[key] = value;
const handlerName = `on${key.charAt(0).toUpperCase() + key.slice(1)}Change` as const;
const handler = this.handlers[handlerName];
if (handler) {
handler(value, oldValue);
}
}
}
===== 14.4 is 操作符(类型保护)=====
==== 14.4.1 自定义类型保护 ====
is 关键字用于创建返回 boolean 的自定义类型保护函数。
// 基本类型保护
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function processValue(value: unknown) {
if (isString(value)) {
// value 被缩小为 string 类型
console.log(value.toUpperCase());
} else if (isNumber(value)) {
// value 被缩小为 number 类型
console.log(value.toFixed(2));
}
}
==== 14.4.2 复杂类型保护 ====
// 接口定义
interface Cat {
type: "cat";
name: string;
meow: () => void;
}
interface Dog {
type: "dog";
name: string;
bark: () => void;
}
interface Bird {
type: "bird";
name: string;
chirp: () => void;
}
type Animal = Cat | Dog | Bird;
// 类型保护函数
function isCat(animal: Animal): animal is Cat {
return animal.type === "cat";
}
function isDog(animal: Animal): animal is Dog {
return animal.type === "dog";
}
function isBird(animal: Animal): animal is Bird {
return animal.type === "bird";
}
// 使用类型保护
function makeSound(animal: Animal): void {
if (isCat(animal)) {
animal.meow();
} else if (isDog(animal)) {
animal.bark();
} else if (isBird(animal)) {
animal.chirp();
}
}
==== 14.4.3 泛型类型保护 ====
// 检查对象是否有某个属性
function hasProperty(
obj: unknown,
prop: T
): obj is { [K in T]: unknown } {
return typeof obj === "object" && obj !== null && prop in obj;
}
// 检查是否为 Promise
function isPromise(value: unknown): value is Promise {
return value instanceof Promise || (
typeof value === "object" &&
value !== null &&
"then" in value &&
typeof (value as Promise).then === "function"
);
}
// 使用
async function resolveValue(value: T | Promise): Promise {
if (isPromise(value)) {
return value;
}
return value;
}
===== 14.5 条件类型 =====
==== 14.5.1 基础语法 ====
条件类型根据类型关系选择两种类型之一,语法类似三元运算符。
// 基本语法: T extends U ? X : Y
type IsString = T extends string ? true : false;
type T1 = IsString; // true
type T2 = IsString; // false
type T3 = IsString<"hello">; // true (字面量类型是 string 的子类型)
// 检查是否继承自某个类型
class Animal {
name!: string;
}
class Dog extends Animal {
bark() {}
}
class Car {
brand!: string;
}
type IsAnimal = T extends Animal ? true : false;
type A = IsAnimal; // true
type B = IsAnimal; // false
==== 14.5.2 分布式条件类型 ====
当条件类型作用于泛型参数时,会自动分布到联合类型的每个成员。
// 分布式条件类型
type ToArray = T extends any ? T[] : never;
// 对联合类型进行分布
type StringOrNumberArray = ToArray;
// string[] | number[] (不是 (string | number)[])
// 防止分布式行为 - 使用元组包裹
type NonDistributed = [T] extends [any] ? T[] : never;
type A = NonDistributed; // (string | number)[]
// 实用示例:过滤联合类型
type Exclude = T extends U ? never : T;
type Extract = T extends U ? T : never;
type T = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type U = Extract<"a" | "b" | "c", "a" | "d">; // "a"
==== 14.5.3 infer 关键字 ====
infer 用于在条件类型中推断类型变量。
// 提取函数返回类型
type ReturnType = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "Alice" };
}
type User = ReturnType; // { id: number; name: string; }
// 提取函数参数类型
type Parameters = T extends (...args: infer P) => any ? P : never;
function updateUser(id: number, data: { name: string }) {}
type UpdateUserParams = Parameters;
// [number, { name: string }]
// 提取数组元素类型
type ElementType = T extends (infer E)[] ? E : T;
type A = ElementType; // string
type B = ElementType; // number[]
// 提取 Promise 返回值
type Awaited = T extends Promise ? R : T;
type P = Awaited>; // string
type N = Awaited; // number
===== 14.6 映射类型 =====
==== 14.6.1 基础映射类型 ====
// 只读类型
type Readonly = {
readonly [P in keyof T]: T[P];
};
// 可选类型
type Partial = {
[P in keyof T]?: T[P];
};
// 必需类型(移除可选)
type Required = {
[P in keyof T]-?: T[P];
};
// 可空类型
type Nullable = {
[P in keyof T]: T[P] | null;
};
// 代理包装
type Proxy = {
[P in keyof T]: { get(): T[P]; set(value: T[P]): void };
};
interface Person {
name: string;
age: number;
}
type ProxiedPerson = Proxy;
// {
// name: { get(): string; set(value: string): void; };
// age: { get(): number; set(value: number): void; };
// }
==== 14.6.2 深度映射类型 ====
// 深度只读
type DeepReadonly = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly
: T[P];
};
// 深度可选
type DeepPartial = {
[P in keyof T]?: T[P] extends object
? DeepPartial
: T[P];
};
// 深度必需
type DeepRequired = {
[P in keyof T]-?: T[P] extends object
? DeepRequired
: T[P];
};
interface Nested {
a: {
b: {
c: string;
};
d: number;
};
}
type ReadonlyNested = DeepReadonly;
// {
// readonly a: {
// readonly b: {
// readonly c: string;
// };
// readonly d: number;
// };
// }
===== 14.7 模板字面量类型 =====
==== 14.7.1 基础模板字面量 ====
// 字符串字面量拼接
type EventName = `on${Capitalize}`;
type ClickEvent = EventName<"click">; // "onClick"
type HoverEvent = EventName<"hover">; // "onHover"
// 组合多个类型
type Position = "top" | "bottom" | "left" | "right";
type Margin = `margin${Capitalize}`;
// "marginTop" | "marginBottom" | "marginLeft" | "marginRight"
// 嵌套模板
type HttpMethod = "get" | "post" | "put" | "delete";
type Endpoint = `/api/${string}`;
type ApiUrl = `${Uppercase} ${Endpoint}`;
==== 14.7.2 模板字面量的模式匹配 ====
// 提取事件名
type GetEvent =
T extends `on${infer Event}` ? Uncapitalize : never;
type E1 = GetEvent<"onClick">; // "click"
type E2 = GetEvent<"onMouseEnter">; // "mouseEnter"
type E3 = GetEvent<"foo">; // never
// 提取 CSS 变量
type ExtractVar =
T extends `var(--${infer Var})` ? Var : never;
type V1 = ExtractVar<"var(--primary-color)">; // "primary-color"
type V2 = ExtractVar<"var(--spacing-md)">; // "spacing-md"
// 复杂模式匹配
type ParseRoute =
T extends `${infer Method} /api/${infer Path}`
? { method: Method; path: Path }
: never;
type Route = ParseRoute<"GET /api/users">;
// { method: "GET"; path: "users"; }
===== 14.8 实用类型组合 =====
==== 14.8.1 组合多个操作符 ====
// 提取对象的可选属性名
type OptionalKeys = {
[K in keyof T]-?: {} extends Pick ? K : never;
}[keyof T];
interface User {
id: number;
name: string;
email?: string;
age?: number;
}
type UserOptionalKeys = OptionalKeys; // "email" | "age"
// 提取对象的可选属性
type OptionalProps = Pick>;
type UserOptional = OptionalProps; // { email?: string; age?: number; }
// 扁平化嵌套对象(只支持一级)
type Flatten = {
[K in keyof T]: T[K];
};
==== 14.8.2 递归类型操作 ====
// 路径字符串生成
type Path =
K extends string
? T[K] extends Record
? `${K}.${Path}` | K
: K
: never;
interface Data {
user: {
name: string;
address: {
city: string;
country: string;
};
};
settings: {
theme: string;
};
}
type DataPaths = Path;
// "user" | "user.name" | "user.address" | "user.address.city" | ...
===== 14.9 本章小结 =====
本章我们学习了:
1. **keyof** - 获取类型的所有键
2. **typeof** - 获取值的类型
3. **in** - 遍历联合类型,创建映射类型
4. **is** - 自定义类型保护函数
5. **条件类型** - 根据类型关系选择类型
6. **infer** - 在条件类型中推断类型
7. **映射类型** - 转换现有类型的属性
8. **模板字面量类型** - 字符串类型的模式操作
===== 14.10 练习题 =====
==== 练习 1:keyof 应用 ====
实现一个函数,能够安全地获取对象的多个属性:
function pluck(obj: T, keys: K[]): Pick {
// 实现
}
interface Product {
id: number;
name: string;
price: number;
category: string;
}
const product: Product = {
id: 1,
name: "Laptop",
price: 999,
category: "Electronics"
};
// 期望:提取 id 和 name
const summary = pluck(product, ["id", "name"]);
// 类型: { id: number; name: string; }
参考答案
function pluck(obj: T, keys: K[]): Pick {
const result = {} as Pick;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
// 或者使用 reduce
function pluck2(obj: T, keys: K[]): Pick {
return keys.reduce((acc, key) => {
acc[key] = obj[key];
return acc;
}, {} as Pick);
}
// 使用
const summary = pluck(product, ["id", "name"]);
console.log(summary); // { id: 1, name: "Laptop" }
==== 练习 2:类型保护实现 ====
实现以下类型和类型保护函数:
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 isSquare(shape: Shape): shape is Square {
return shape.kind === "square";
}
function isRectangle(shape: Shape): shape is Rectangle {
return shape.kind === "rectangle";
}
function isCircle(shape: Shape): shape is Circle {
return shape.kind === "circle";
}
// 计算面积
function calculateArea(shape: Shape): number {
if (isSquare(shape)) {
return shape.size ** 2;
} else if (isRectangle(shape)) {
return shape.width * shape.height;
} else if (isCircle(shape)) {
return Math.PI * shape.radius ** 2;
}
// 穷尽检查
const _exhaustive: never = shape;
return _exhaustive;
}
// 使用
const shapes: Shape[] = [
{ kind: "square", size: 5 },
{ kind: "rectangle", width: 4, height: 6 },
{ kind: "circle", radius: 3 }
];
shapes.forEach(shape => {
console.log(`${shape.kind} area:`, calculateArea(shape));
});
==== 练习 3:条件类型与 infer ====
实现以下类型工具:
// 1. 提取数组的嵌套层级深度
type ArrayDepth = any;
// 2. 提取函数的第 N 个参数类型
type NthParameter = any;
// 3. 将联合类型转换为交叉类型
type UnionToIntersection = any;
参考答案
// 1. 数组嵌套深度
type ArrayDepth =
T extends (infer E)[]
? ArrayDepth
: Depth["length"];
type D1 = ArrayDepth; // 0
type D2 = ArrayDepth; // 1
type D3 = ArrayDepth; // 2
// 2. 第 N 个参数
type NthParameter =
T extends (...args: infer P) => any
? P[N]
: never;
function example(a: string, b: number, c: boolean) {}
type P1 = NthParameter; // string
type P2 = NthParameter; // number
// 3. 联合转交叉
type UnionToIntersection =
(T extends any ? (x: T) => void : never) extends
(x: infer R) => void ? R : never;
type U2I = UnionToIntersection<{ a: 1 } | { b: 2 }>;
// { a: 1 } & { b: 2 }
==== 练习 4:映射类型实战 ====
实现一个类型安全的表单验证系统:
// 要求:
// 1. 根据数据模型自动生成验证规则类型
// 2. 支持必填、最小长度、正则等验证规则
// 3. 验证函数返回详细的错误信息
参考答案
// 验证规则类型
interface ValidationRule {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
validator?: (value: T) => string | undefined;
}
// 字段验证器
type FieldValidators = {
[K in keyof T]?: ValidationRule;
};
// 验证错误
type ValidationErrors = {
[K in keyof T]?: string;
};
// 验证器类
class Validator> {
constructor(private rules: FieldValidators) {}
validate(data: T): { valid: boolean; errors: ValidationErrors } {
const errors: ValidationErrors = {};
for (const key in this.rules) {
const rule = this.rules[key];
const value = data[key];
if (!rule) continue;
// 必填检查
if (rule.required && (value === undefined || value === null || value === "")) {
errors[key] = `${String(key)} is required`;
continue;
}
// 字符串验证
if (typeof value === "string") {
if (rule.minLength !== undefined && value.length < rule.minLength) {
errors[key] = `${String(key)} must be at least ${rule.minLength} characters`;
continue;
}
if (rule.maxLength !== undefined && value.length > rule.maxLength) {
errors[key] = `${String(key)} must be at most ${rule.maxLength} characters`;
continue;
}
if (rule.pattern && !rule.pattern.test(value)) {
errors[key] = `${String(key)} format is invalid`;
continue;
}
}
// 自定义验证
if (rule.validator) {
const error = rule.validator(value);
if (error) {
errors[key] = error;
}
}
}
return {
valid: Object.keys(errors).length === 0,
errors
};
}
}
// 使用示例
interface UserForm {
username: string;
email: string;
age: number;
}
const userValidator = new Validator({
username: {
required: true,
minLength: 3,
maxLength: 20
},
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
age: {
required: true,
validator: (value) => value < 18 ? "Must be at least 18" : undefined
}
});
// 验证
const result1 = userValidator.validate({
username: "ab",
email: "invalid-email",
age: 16
});
// result1.valid = false
// result1.errors = { username: "...", email: "...", age: "..." }
const result2 = userValidator.validate({
username: "alice",
email: "alice@example.com",
age: 25
});
// result2.valid = true
===== 扩展阅读 =====
- [[TypeScript:第十五章_工具类型|下一章:工具类型]]
- [[https://www.typescriptlang.org/docs/handbook/2/keyof-types.html|TypeScript 官方文档 - keyof 类型操作符]]
- [[https://www.typescriptlang.org/docs/handbook/2/conditional-types.html|TypeScript 官方文档 - 条件类型]]