如何扩展TypeScript比较器的类型签名以支持嵌套对象属性排序?
扩展支持嵌套属性的
alphabeticallyBy函数 我们可以从类型定义和函数逻辑两部分入手,让原函数支持数组或字符串格式的嵌套属性路径,同时保证TypeScript的类型安全。
一、定义嵌套路径的类型工具
先实现两个核心类型工具,用来提取对象的所有嵌套属性路径,并支持数组与点分隔字符串两种格式:
// 提取对象所有嵌套属性的数组路径类型 type NestedKeyof<T> = T extends object ? { [K in keyof T]: [K] | [K, ...NestedKeyof<T[K]>] }[keyof T] : []; // 将数组路径转换为点分隔的字符串路径 type PathToString<T extends any[]> = T extends [infer First, ...infer Rest] ? First extends string ? Rest extends any[] ? `${First}.${PathToString<Rest>}` : First : never : never; // 合并支持数组路径和字符串路径的联合类型 type Path<T> = NestedKeyof<T> | PathToString<NestedKeyof<T>>;
二、实现嵌套属性取值工具函数
需要一个工具函数,根据传入的路径(数组或字符串)从对象中取出嵌套属性值:
function getNestedValue<T, P extends Path<T>>(obj: T, path: P): string | undefined { const keys = Array.isArray(path) ? path : path.split('.'); let current: any = obj; for (const key of keys) { if (current == null) return undefined; current = current[key]; } return typeof current === 'string' ? current : undefined; }
三、重构alphabeticallyBy函数
基于上面的类型和工具函数,重构原函数以支持嵌套路径:
function alphabeticallyBy<T>(path: Path<T>) { return (a: Partial<T>, b: Partial<T>): number => { const valA = getNestedValue(a, path as Path<Partial<T>>); const valB = getNestedValue(b, path as Path<Partial<T>>); // 处理属性不存在的情况,默认将undefined/null排在前面 if (valA == null && valB == null) return 0; if (valA == null) return -1; if (valB == null) return 1; return valA.localeCompare(valB); }; }
四、使用示例
现在可以按照你期望的两种方式调用:
// 示例对象类型 interface User { name: string; address: { city: string; street: { name: string; }; }; } const users: User[] = [ { name: "Bob", address: { city: "New York", street: { name: "5th Ave" } } }, { name: "Alice", address: { city: "London", street: { name: "Oxford St" } } }, ]; // 数组路径形式 users.sort(alphabeticallyBy(["address", "street", "name"])); // 字符串路径形式 users.sort(alphabeticallyBy("address.city"));
类型安全说明
TypeScript会自动校验路径的合法性,如果传入不存在的属性路径,会直接抛出类型错误,避免运行时异常。
内容的提问来源于stack exchange,提问作者Nick Schmitt
相关产品推荐
相关产品推荐

