TypeScript 提供了丰富的类型操作符,使我们能够灵活地查询、转换和操作类型。本章将详细介绍 keyof、typeof、in、is 等查询操作符,以及条件类型、映射类型等高级类型操作技术。掌握这些操作符,将使你能够编写出更加灵活和强大的类型定义。
keyof 操作符用于获取类型的所有键组成的联合类型。
interface Person {
name: string;
age: number;
email: string;
}
type PersonKeys = keyof Person;
// 等价于: "name" | "age" | "email"
// 使用场景:类型安全的属性访问
function getProperty<T, K extends keyof T>(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 的键
// 字符串索引签名
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"
// 实现类型安全的对象选择
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
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 }
在类型上下文中,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
// 使用 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; }
// 运行时配置对象
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<T>(
path: EndpointPaths,
options?: RequestInit
): Promise<T> {
const response = await fetch(path, options);
return response.json();
}
// 使用
interface User {
id: number;
name: string;
}
const users = await fetchApi<User[]>(API_ENDPOINTS.users);
in 操作符用于遍历联合类型的每个成员,常用于创建映射类型。
// 基础映射类型 - 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 将所有属性变为必需
type Required<T> = {
[P in keyof T]-?: T[P]; // -? 移除可选修饰符
};
// 只读数组类型
type ReadonlyArray<T> = {
readonly [P in keyof T[]]: T[P];
};
// 使用 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 StringKeys<T> = {
[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<Mixed>;
// { name: string; email: string; }
// 根据属性创建对应的事件处理器类型
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]?: (
newValue: T[K],
oldValue: T[K]
) => void;
};
interface Settings {
theme: "light" | "dark";
fontSize: number;
notifications: boolean;
}
type SettingsEventHandlers = EventHandlers<Settings>;
// {
// 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<Settings> = {};
on<K extends keyof EventHandlers<Settings>>(
event: K,
handler: EventHandlers<Settings>[K]
): void {
this.handlers[event] = handler;
}
set<K extends keyof Settings>(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);
}
}
}
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));
}
}
// 接口定义
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();
}
}
// 检查对象是否有某个属性
function hasProperty<T extends string>(
obj: unknown,
prop: T
): obj is { [K in T]: unknown } {
return typeof obj === "object" && obj !== null && prop in obj;
}
// 检查是否为 Promise
function isPromise<T>(value: unknown): value is Promise<T> {
return value instanceof Promise || (
typeof value === "object" &&
value !== null &&
"then" in value &&
typeof (value as Promise<T>).then === "function"
);
}
// 使用
async function resolveValue<T>(value: T | Promise<T>): Promise<T> {
if (isPromise<T>(value)) {
return value;
}
return value;
}
条件类型根据类型关系选择两种类型之一,语法类似三元运算符。
// 基本语法: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type T1 = IsString<string>; // true
type T2 = IsString<number>; // false
type T3 = IsString<"hello">; // true (字面量类型是 string 的子类型)
// 检查是否继承自某个类型
class Animal {
name!: string;
}
class Dog extends Animal {
bark() {}
}
class Car {
brand!: string;
}
type IsAnimal<T> = T extends Animal ? true : false;
type A = IsAnimal<Dog>; // true
type B = IsAnimal<Car>; // false
当条件类型作用于泛型参数时,会自动分布到联合类型的每个成员。
// 分布式条件类型 type ToArray<T> = T extends any ? T[] : never; // 对联合类型进行分布 type StringOrNumberArray = ToArray<string | number>; // string[] | number[] (不是 (string | number)[]) // 防止分布式行为 - 使用元组包裹 type NonDistributed<T> = [T] extends [any] ? T[] : never; type A = NonDistributed<string | number>; // (string | number)[] // 实用示例:过滤联合类型 type Exclude<T, U> = T extends U ? never : T; type Extract<T, U> = T extends U ? T : never; type T = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type U = Extract<"a" | "b" | "c", "a" | "d">; // "a"
infer 用于在条件类型中推断类型变量。
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: "Alice" };
}
type User = ReturnType<typeof getUser>; // { id: number; name: string; }
// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function updateUser(id: number, data: { name: string }) {}
type UpdateUserParams = Parameters<typeof updateUser>;
// [number, { name: string }]
// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : T;
type A = ElementType<string[]>; // string
type B = ElementType<number[][]>; // number[]
// 提取 Promise 返回值
type Awaited<T> = T extends Promise<infer R> ? R : T;
type P = Awaited<Promise<string>>; // string
type N = Awaited<number>; // number
// 只读类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 可选类型
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 必需类型(移除可选)
type Required<T> = {
[P in keyof T]-?: T[P];
};
// 可空类型
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 代理包装
type Proxy<T> = {
[P in keyof T]: { get(): T[P]; set(value: T[P]): void };
};
interface Person {
name: string;
age: number;
}
type ProxiedPerson = Proxy<Person>;
// {
// name: { get(): string; set(value: string): void; };
// age: { get(): number; set(value: number): void; };
// }
// 深度只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// 深度可选
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
// 深度必需
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object
? DeepRequired<T[P]>
: T[P];
};
interface Nested {
a: {
b: {
c: string;
};
d: number;
};
}
type ReadonlyNested = DeepReadonly<Nested>;
// {
// readonly a: {
// readonly b: {
// readonly c: string;
// };
// readonly d: number;
// };
// }
// 字符串字面量拼接
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type HoverEvent = EventName<"hover">; // "onHover"
// 组合多个类型
type Position = "top" | "bottom" | "left" | "right";
type Margin = `margin${Capitalize<Position>}`;
// "marginTop" | "marginBottom" | "marginLeft" | "marginRight"
// 嵌套模板
type HttpMethod = "get" | "post" | "put" | "delete";
type Endpoint = `/api/${string}`;
type ApiUrl = `${Uppercase<HttpMethod>} ${Endpoint}`;
// 提取事件名
type GetEvent<T extends string> =
T extends `on${infer Event}` ? Uncapitalize<Event> : never;
type E1 = GetEvent<"onClick">; // "click"
type E2 = GetEvent<"onMouseEnter">; // "mouseEnter"
type E3 = GetEvent<"foo">; // never
// 提取 CSS 变量
type ExtractVar<T extends string> =
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 string> =
T extends `${infer Method} /api/${infer Path}`
? { method: Method; path: Path }
: never;
type Route = ParseRoute<"GET /api/users">;
// { method: "GET"; path: "users"; }
// 提取对象的可选属性名
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
interface User {
id: number;
name: string;
email?: string;
age?: number;
}
type UserOptionalKeys = OptionalKeys<User>; // "email" | "age"
// 提取对象的可选属性
type OptionalProps<T> = Pick<T, OptionalKeys<T>>;
type UserOptional = OptionalProps<User>; // { email?: string; age?: number; }
// 扁平化嵌套对象(只支持一级)
type Flatten<T> = {
[K in keyof T]: T[K];
};
// 路径字符串生成
type Path<T, K extends keyof T = keyof T> =
K extends string
? T[K] extends Record<string, any>
? `${K}.${Path<T[K]>}` | K
: K
: never;
interface Data {
user: {
name: string;
address: {
city: string;
country: string;
};
};
settings: {
theme: string;
};
}
type DataPaths = Path<Data>;
// "user" | "user.name" | "user.address" | "user.address.city" | ...
本章我们学习了:
1. keyof - 获取类型的所有键
2. typeof - 获取值的类型
3. in - 遍历联合类型,创建映射类型
4. is - 自定义类型保护函数
5. 条件类型 - 根据类型关系选择类型
6. infer - 在条件类型中推断类型
7. 映射类型 - 转换现有类型的属性
8. 模板字面量类型 - 字符串类型的模式操作
实现一个函数,能够安全地获取对象的多个属性:
function pluck<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
// 实现
}
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<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
// 或者使用 reduce
function pluck2<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
return keys.reduce((acc, key) => {
acc[key] = obj[key];
return acc;
}, {} as Pick<T, K>);
}
// 使用
const summary = pluck(product, ["id", "name"]);
console.log(summary); // { id: 1, name: "Laptop" }
实现以下类型和类型保护函数:
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));
});
实现以下类型工具:
// 1. 提取数组的嵌套层级深度 type ArrayDepth<T> = any; // 2. 提取函数的第 N 个参数类型 type NthParameter<T, N extends number> = any; // 3. 将联合类型转换为交叉类型 type UnionToIntersection<T> = any;
参考答案
// 1. 数组嵌套深度
type ArrayDepth<T, Depth extends number[] = []> =
T extends (infer E)[]
? ArrayDepth<E, [...Depth, 1]>
: Depth["length"];
type D1 = ArrayDepth<number>; // 0
type D2 = ArrayDepth<number[]>; // 1
type D3 = ArrayDepth<number[][]>; // 2
// 2. 第 N 个参数
type NthParameter<T, N extends number> =
T extends (...args: infer P) => any
? P[N]
: never;
function example(a: string, b: number, c: boolean) {}
type P1 = NthParameter<typeof example, 0>; // string
type P2 = NthParameter<typeof example, 1>; // number
// 3. 联合转交叉
type UnionToIntersection<T> =
(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 }
实现一个类型安全的表单验证系统:
// 要求: // 1. 根据数据模型自动生成验证规则类型 // 2. 支持必填、最小长度、正则等验证规则 // 3. 验证函数返回详细的错误信息
参考答案
// 验证规则类型
interface ValidationRule<T> {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
validator?: (value: T) => string | undefined;
}
// 字段验证器
type FieldValidators<T> = {
[K in keyof T]?: ValidationRule<T[K]>;
};
// 验证错误
type ValidationErrors<T> = {
[K in keyof T]?: string;
};
// 验证器类
class Validator<T extends Record<string, any>> {
constructor(private rules: FieldValidators<T>) {}
validate(data: T): { valid: boolean; errors: ValidationErrors<T> } {
const errors: ValidationErrors<T> = {};
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<UserForm>({
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