如何通过TypeScript类型签名约束排序函数的对象属性类型?
问题描述
我正在实现一个类Lodash sortBy 的排序比较函数,要求:
- 参与比较的两个对象必须是同一类型
- 指定用于排序的属性类型只能是
string或number - 两个对象的该属性类型必须完全一致(要么都是string,要么都是number)
我希望通过TypeScript的类型签名来实现这些约束,替代当前代码里的运行时检查,但不知道具体怎么写泛型和类型约束。当前的实现代码如下:
compareObjectsForSorting(a: object, b: object, propertyName: string, sortDescending = false): number { /* First, some safety checks to make sure the arguments match what's intended. Check that the desired property is even present on both objects, and throw an error if not */ [a, b].forEach((obj) => { if (!Object.keys(obj).includes(propertyName)) { throw new Error(propertyName + 'not found in object' + obj); } }); /* Let's just stick to comparing numbers and strings */ const typeOfPropA = typeof a[propertyName]; const typeOfPropB = typeof b[propertyName]; [typeOfPropA, typeOfPropB].forEach((t) => { if (t !== 'string' && t !== 'number') { throw new Error(`${propertyName} is of type ${t}. Please compare only strings or numbers.`); } }); /* And finally, let's make sure the same property is of the same type in both objects */ if (typeOfPropA !== typeOfPropB) { throw new Error(`Unable to compare. ${propertyName} is a ${typeof a[propertyName]} in one object, and a ${typeof b[propertyName]} in another.`); } // ... and then comes the actual logic of comparing, not shown here. }
解决方案
可以通过泛型+索引类型约束实现这些要求,让TypeScript在编译阶段就完成类型检查,无需额外运行时判断:
function compareObjectsForSorting<T, K extends keyof T>( a: T, b: T, propertyName: K, sortDescending = false ): number { const valA = a[propertyName]; const valB = b[propertyName]; // 仅当T为any类型时需要兜底检查,明确类型可省略 if (typeof valA !== 'string' && typeof valA !== 'number') { throw new Error(`${String(propertyName)} must be string or number`); } // 实际比较逻辑 let result: number; if (typeof valA === 'string') { result = valA.localeCompare(valB as string); } else { result = valA - valB; } return sortDescending ? -result : result; }
关键类型说明
- 泛型
T:强制a和b为同一类型,确保两者属性结构完全一致。 K extends keyof T:约束propertyName必须是T的有效属性名,避免传入不存在的属性。- 自动类型推导:传入具体对象和属性名时,TS会自动推导
T和K的类型,同时校验T[K]是否为string/number——不符合要求的话编译阶段直接报错。
使用示例
interface User { name: string; age: number; } const user1: User = { name: "Alice", age: 30 }; const user2: User = { name: "Bob", age: 25 }; // 合法调用:属性存在且类型符合要求 compareObjectsForSorting(user1, user2, "name"); compareObjectsForSorting(user1, user2, "age", true); // 编译报错:属性不存在 compareObjectsForSorting(user1, user2, "invalidProp"); // 编译报错:属性类型不是string/number interface BadUser { active: boolean; } const u1: BadUser = { active: true }; const u2: BadUser = { active: false }; compareObjectsForSorting(u1, u2, "active");
补充说明
如果你的T都是明确的接口类型,编译阶段的检查已经足够,可以完全移除运行时的类型判断;只有当T可能为any时,才需要保留兜底检查。
内容的提问来源于stack exchange,提问作者Andy Bonner
相关产品推荐
相关产品推荐

