TypeScript移除对象数组重复项时,如何约束键对应值为string/number类型以解决类型错误
Great question! The core issue here is that TypeScript doesn’t automatically know T[K] will be a valid object key type (string/number/symbol) by default. Let’s fix this by adding precise type constraints while keeping your original logic completely intact.
Step 1: Restrict Valid Property Types with Generic Constraints
We need to ensure the value of T[K] is one of the allowed types for object keys. You can refine the K generic to only include keys of T where the corresponding value falls into string | number | symbol.
There are two clean ways to do this, depending on your TypeScript version:
Option 1: TypeScript 4.9+ (Using where Clause for Readability)
The where clause makes the constraint straightforward and easy to read:
function removeDuplicates<T, K extends keyof T where T[K] extends string | number | symbol>(arr: T[], propName: K) { const newArr: T[] = []; const lookup: Record<T[K], T> = {} as Record<T[K], T>; // Assert empty object as valid Record for (let i in arr) { lookup[arr[i][propName]] = arr[i]; } for (let i in lookup) { newArr.push(lookup[i]); } return newArr; }
Option 2: Compatible with Older TypeScript Versions
If you’re using a version before 4.9, use a conditional type to filter valid keys:
function removeDuplicates<T, K extends keyof T & (T[K] extends string | number | symbol ? K : never)>(arr: T[], propName: K) { const newArr: T[] = []; const lookup: Record<T[K], T> = {} as Record<T[K], T>; for (let i in arr) { lookup[arr[i][propName]] = arr[i]; } for (let i in lookup) { newArr.push(lookup[i]); } return newArr; }
Step 2: Fix the lookup Type
- We replaced
Partial<Record<T[K], T>>withRecord<T[K], T>: Partial would make lookup values optional, which would cause errors when pushing tonewArr. Since we’ll be populating the lookup dictionary, a full Record type is correct. - We use
as Record<T[K], T>to assert the empty object{}is a valid starting point. This is safe because we immediately start adding entries to it in the loop.
How This Enforces Your Usage Rules
- The generic constraint ensures developers can only pass a
propNamewhose corresponding value is a valid object key. If they try to use a key with a value likeobjectorarray, TypeScript throws a compile-time error—exactly what you want to enforce your intended usage. - Your original logic stays identical; we’re just adding type safety to prevent invalid inputs.
Example Usage
Let’s test this with a sample type to see it in action:
interface User { id: number; username: string; preferences: { theme: string }; // This value is an object, so it can't be used as a key } const users: User[] = [ { id: 1, username: "alice123", preferences: { theme: "dark" } }, { id: 2, username: "bob456", preferences: { theme: "light" } }, { id: 1, username: "alice_dup", preferences: { theme: "dark" } } ]; // Valid: Uses "id" (number, a valid key type) const uniqueUsers = removeDuplicates(users, "id"); // Invalid: TypeScript throws an error, since "preferences" value is an object removeDuplicates(users, "preferences");
内容的提问来源于stack exchange,提问作者Vishal

