问题
Let's say I have the generic function:
function f<T, K extends keyof T>(obj: T, key: K) {
...
}
I would like to enforce the type of T[K] so I could perform type specific actions. For example, using string:
function f<T, K extends keyof T>(obj: T, key: K): string {
return obj[key].toLowerCase();
}
Is this at all possible without casting things to any?
Edit: To clarify, I'm looking for the ability to disallow certain keys based on the resulting type. Using the example above, something like:
f({a: 123, b: 'abc'}, 'b') //No errors
f({a: 123, b: 'abc'}, 'a') //Typescript error, T['a'] is not string
回答1:
To restrict property names to only those having string value types you can use conditional types in conjunction with mapped types:
type StringProperties<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];
declare function f<T>(obj: T, key: StringProperties<T>): string;
f({a: 123, b: 'abc'}, 'b') // No errors
f({a: 123, b: 'abc'}, 'a') // Error: Argument of type '"a"' is not assignable to parameter of type '"b"'.
Playground
回答2:
You can enforce the property types by adding an indexed type, forcing all properties to be of one type:
interface AllStringProps {
[key: string]: string;
}
function f<T extends AllStringProps, K extends keyof T>(obj: T, key: K): string {
return obj[key].toLowerCase();
}
However this contstrains you to use an index type that accepts any string as property.
To add a little bit more safety you could change the snippet to
type AllStringProps<T> = {
[key in keyof T]: string;
}
function f<T extends AllStringProps<T>, K extends keyof T>(obj: T, key: K): string {
return obj[key].toLowerCase();
}
Here the type AllStringProps enforces that all keys available in T must be of type string. The snippet before forced all possible properties must be of type string.
In your use case, not a big difference, but I always prefer the smallest possible constraint.
Update to a question in the comments
Lets say, we only want to allow a subset of T's keys.
We could start by defining all keys which we want to allow:
type AllowedKeys = { "s1" , "s2" };
type AllStringProps<AllowedKeys> = {
[key in keyof AllowedKeys]: string;
}
function f<T extends AllStringProps<AllowedKeys>,
K extends keyof AllowedKeys>(obj: T, key: K): string {
return obj[key].toLowerCase();
}
The snippet above would allow passing the following object:
let t1 = {
s1: "Hello",
s2: "World",
other: 4
}
But not this one
let t1 = {
s1: "Hello",
s2: 6,
other: 4
}
来源:https://stackoverflow.com/questions/51476103/enforcing-type-of-key-in-generic-function-input