I want to filter keyof T
based on type of T[keyof T]
It should work like this:
type KeyOfType<T, U> = ...
KeyOfType<{a: 1, b: '', c: 0, d: () => 1}, number> === 'a' | 'c'
KeyOfType<{a: 1, b: '', c: 0: d: () => 1}, string> === 'b'
KeyOfType<{a: 1, b: '', c: 0: d: () => 1}, Function> === 'd'
Is this possible?
Enter TypeScript 2.1 and the new keyof operator. It queries the set of keys for a given type, which is why it's also called an index type query. Let's assume we have defined the following Todo interface: We can apply the keyof operator to the Todo type to get back a type representing all its property keys, which is a union of string literal types:
We need to provide a little more type information to make that possible. Enter TypeScript 2.1 and the new keyof operator. It queries the set of keys for a given type, which is why it's also called an index type query. Let's assume we have defined the following Todo interface:
It’s enough to know that TypeScript allows you to take an existing type and slightly modify it to make a new type. This is part of its Turing Completeness. You can think of type as *function *— it takes another type as input, makes some calculations and produces new type as output.
The return type is still inferred to be any, however: Without further information, TypeScript can't know which value will be passed for the key parameter, so it can't infer a more specific return type for the prop function. We need to provide a little more type information to make that possible. Enter TypeScript 2.1 and the new keyof operator.
You can use the as
clause of a mapped type to filter the keys of a type:
type KeyOfType<T, V> = keyof {
[P in keyof T as T[P] extends V? P: never]: any
}
The as
clause of a mapped type allows us to manipulate the name of the key. If the key is mapped to never
the key gets removed from the resulting type. You can find more info in this PR
This version still works in post 4.1, just the other way is easier to read.
You can do this using conditional and mapped types
type KeyOfType<T, U> = {[P in keyof T]: T[P] extends U ? P: never}[keyof T]
Let's break things down a little bit.
We can start with a mapped type, that has the same properties of the same type as the original T
, this a simple standard mapped type:
type KeyOfType<T> = { [P in keyof T]: T[P] } // New Type same as the original
T[P]
is a type query and means the type of the key P
in type T
. We can change this to just P
, meaning that the type of the new property is the same as it's name:
type KeyOfType<T> = { [P in keyof T]: P }
// So
KeyOfType<{ a: number, b: string }> == { a: 'a', b: 'b' }
We can add a type query to this type to again get all the keys of the type. Generally a construct T[keyof T]
gets all the property types of a type. Applying this to our mapped type which has the property types the same as the key names we basically get back to keyof T
:
type KeyOfType<T> = { [P in keyof T]: P }[keyof T]
// So
KeyOfType<{ a: number, b: string }> == 'a'|'b'
Now we can add a conditional type to o not always select P
. Since A | never == A
we can set the type of the property in the mapped type to never if the type of the original property (T[P]
) does not meet a certain constraint.
To express the constraint we add an extra generic parameter U
and we use a conditional type which has the form T extends U ? TypeIfTrue: TYpeIfFalse
. Putting it together we get:
type KeyOfType<T, U> = {[P in keyof T]: T[P] extends U ? P: never}[keyof T]
awesome explanation!!
I would also add that you can set a default type to U
to be any key of T
like so:
type Keys<T> = {[P in keyof T]: T[P]}[typeof P]
type KeyOfType<T, U = Keys<T>> = {[P in keyof T]: T[P] extends U ? P : never}[keyof T]
I add U = Keys<T>
to the generic definition of KeyOfType
Thanks for the explanation!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With