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]
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…