泛型(Generics)是 TypeScript 中实现类型参数化的核心机制,它允许我们创建可重用的组件,同时保持类型安全性。我们可以通过一个简单的例子来回顾泛型的基本用法:
function identity<T>(arg: T): T {
return arg;
}
// 使用示例
let output1 = identity<string>("Hello Generics");
let output2 = identity(42); // 类型推断
在这个示例中,
表示类型参数,它会在函数被调用时根据传入参数的类型自动推断。泛型的主要优势在于:
keyof
是 TypeScript 中的类型操作符,用于获取对象类型所有属性键的联合类型。它的核心作用可以概括为:
将对象类型的属性键提升到类型层面,使得我们可以进行基于属性名的类型操作。
interface Person {
name: string;
age: number;
address: string;
}
type PersonKeys = keyof Person; // "name" | "age" | "address"
这个简单的示例展示了 keyof
的基本工作方式。但它的真正威力需要结合泛型才能完全展现出来。
当泛型遇到 keyof
时,TypeScript 的类型系统实现了从一维到二维的跃升:
特性 | 普通泛型 | 结合 keyof 的泛型 |
---|---|---|
类型参数 | 单一类型 | 类型与属性键的组合 |
类型约束 | 简单类型限制 | 基于对象结构的精确约束 |
类型推断 | 基于参数类型 | 基于对象属性结构的智能推断 |
应用场景 | 通用容器、简单函数 | 复杂类型操作、高级模式匹配 |
这种维度提升使得我们可以创建更加强大和灵活的类型约束系统。
keyof
约束的基本语法形式为:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
这里包含三个关键要素:
T
表示对象类型,K
表示属性键类型K extends keyof T
确保 K 必须是 T 的合法属性键T[K]
表示对应属性值的类型在约束链中,各类型参数之间存在明确的依赖关系:
function processObject<T extends Record<string, any>, K extends keyof T>(
obj: T,
keys: K[]
): Pick<T, K> {
// 实现逻辑
}
这个示例展示了多级约束的典型模式:
T extends Record
确保 T 是对象类型K extends keyof T
确保 K 是 T 的合法键Pick
使用内置工具类型获取子集类型完整的约束层次可以分为四个级别:
基础约束:确保参数是对象类型
<T extends object>
键名约束:限制键名的范围
<K extends keyof T>
值类型约束:进一步限制属性值的类型
<K extends keyof T, T[K] extends number>
复合约束:组合多个约束条件
<T extends { id: string }, K extends keyof T>
问题场景:直接访问对象属性可能导致运行时错误
// 不安全的方式
function unsafeGet(obj: any, key: string) {
return obj[key]; // 没有类型检查
}
keyof 解决方案:
function safeGet<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
interface User {
id: number;
name: string;
email: string;
}
const user: User = { id: 1, name: "Alice", email: "[email protected]" };
// 正确的使用
console.log(safeGet(user, "name")); // 正确
// console.log(safeGet(user, "age")); // 编译错误:类型"age"的参数不能赋给类型"id" | "name" | "email"的参数
应用场景:需要根据运行时条件处理不同属性
function updateProperty<T, K extends keyof T>(
obj: T,
key: K,
value: T[K]
): T {
return { ...obj, [key]: value };
}
const updatedUser = updateProperty(user, "email", "[email protected]");
类型安全性分析:
模式实现:创建属性映射处理器
type MappedProcessor<T> = {
[K in keyof T]: (value: T[K]) => void;
};
function createProcessor<T>(processor: MappedProcessor<T>) {
return processor;
}
// 使用示例
const userProcessor = createProcessor<User>({
id: (value) => console.log(`Processing ID: ${value}`),
name: (value) => console.log(`Processing Name: ${value.toUpperCase()}`),
email: (value) => console.log(`Sending email to ${value}`)
});
自定义类型守卫:
function hasProperty<T, K extends keyof T>(
obj: unknown,
key: K
): obj is T {
return (obj as T)[key] !== undefined;
}
// 使用示例
if (hasProperty<User, "id>(unknownObj, "id")) {
console.log(unknownObj.id); // 安全访问
}
对象工厂实现:
class GenericFactory<T extends object> {
private defaultValue: T;
constructor(defaults: T) {
this.defaultValue = defaults;
}
create<K extends keyof T>(overrides: Pick<T, K>): T {
return { ...this.defaultValue, ...overrides };
}
}
// 使用示例
const userFactory = new GenericFactory<User>({
id: 0,
name: "Guest",
email: "[email protected]"
});
const newUser = userFactory.create({ name: "Bob" });
深度可选类型转换:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
interface Company {
name: string;
address: {
street: string;
city: string;
};
}
type PartialCompany = DeepPartial<Company>;
// 合法的部分对象
const partialCompany: PartialCompany = {
address: {
city: "New York"
}
};
类型过滤示例:
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
interface MixedData {
id: number;
name: string;
timestamp: Date;
description: string;
}
type StringFields = StringKeys<MixedData>; // "name" | "description"
深度只读类型:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
const config: DeepReadonly<AppConfig> = {
api: {
endpoint: "https://api.example.com",
timeout: 5000
}
};
// config.api.timeout = 1000; // 错误:只读属性
增强的类型守卫:
function isKeyOf<T>(obj: T, key: string | number | symbol): key is keyof T {
return key in obj;
}
// 使用示例
const testKey = "age";
if (isKeyOf(user, testKey)) {
console.log(user[testKey]); // 安全访问
}
问题代码:
// 可能导致过多类型实例化
function processKeys<T>(obj: T) {
type Keys = keyof T;
// ...
}
优化方案:
// 使用约束提前限制类型范围
function optimizedProcess<T extends Record<string, any>>(obj: T) {
type Keys = keyof T;
// ...
}
类型记忆模式:
type Memoized<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
interface ComplexType {
// 复杂类型定义
}
// 使用缓存类型
type CachedType = Memoized<ComplexType>;
安全访问封装:
function safeAccess<T, K extends keyof T>(obj: T, key: K): T[K] | undefined {
try {
return obj[key];
} catch (e) {
console.error(`Access error for key ${String(key)}`);
return undefined;
}
}
运行时类型捕获:
const colors = {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF"
};
type ColorKeys = keyof typeof colors; // "red" | "green" | "blue"
动态类型生成:
type DynamicMapper<T extends string> = {
[K in T]: K;
};
type MappedKeys = DynamicMapper<keyof User>; // { id: "id"; name: "name"; email: "email" }
高级类型转换:
type ValueType<T, K extends keyof T> = T[K] extends (infer U)[] ? U : never;
interface Collection {
users: User[];
products: Product[];
}
type UserType = ValueType<Collection, "users">; // User
问题描述:如何在保持 keyof 约束的同时扩展类型
解决方案:
interface Base {
id: number;
}
function extendedFunction<T extends Base, K extends keyof T>(obj: T, key: K) {
// 可以访问 id 和其他属性
}
模式实现:
type CircularReference<T> = {
[K in keyof T]: T[K] extends object ? CircularReference<T[K]> : T[K];
};
interface TreeNode {
value: number;
children: TreeNode[];
}
type ProcessedNode = CircularReference<TreeNode>;
类型断言策略:
interface ExternalType {
// 未知结构
}
function handleExternal<T extends ExternalType, K extends keyof T>(
obj: T,
key: K
) {
// 安全处理逻辑
}
在实际开发中,建议:
keyof
约束