In TypeScript, some types are defined using extends keyof
or in keyof
. I have tried to understand what they mean, but so far I didn't succeed.
What I got is that keyof
alone returns a union type which has all the names as possible values that are existent as property names on the type that you specify after keyof
.
type T = keyof string;
T
therefor is equivalent to startsWith | endsWith | trim | substring | ...
.
Is this correct?
Now, if I try to think about what extends keyof
and in keyof
mean, my gut feeling says the following:
extends keyof is any type that derives from T, i.e. it has all these possible values, but maybe more.
in keyof is any type that takes values from T, but not necessarily all of them (it's possible, but maybe less).
So, from this POV extends keyof
would describe a >=
relation, in keyof
would describe a <=
relation. Is this correct? If not, what would be correct?
For any type T
, keyof T
is the union of known, public property names of T
.
Example:
interface Person {
age: number;
name: string;
}
type PersonKeys = keyof Person; // "age" | "name"
Your assumption that keyof string
yields startsWith | endsWith | trim | ...
is therefore correct. You can learn more about it in the lookup type release notes.
extends keyof
extends
, in this case, is used to constrain the type of a generic parameter. Example:
<T, K extends keyof T>
K
can therefore only be a public property name of T
. It has nothing to do with extending a type or inheritance, contrary to extending interfaces.
A usage of extends keyof
could be the following:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = {
age: 22,
name: "Tobias",
};
// name is a property of person
// --> no error
const name = getProperty(person, "name");
// gender is not a property of person
// --> error
const gender = getProperty(person, "gender");
Aside from the documentation on index types, I found this helpful article.
in keyof
in
is used when we're defining an index signature that we want to type with a union of string, number or symbol literals. In combination with keyof
we can use it to create a so called mapped type, which re-maps all properties of the original type.
A usage of in keyof
could be the following:
type Optional<T> = {
[K in keyof T]?: T[K]
};
const person: Optional<Person> = {
name: "Tobias"
// notice how I do not have to specify an age,
// since age's type is now mapped from 'number' to 'number?'
// and therefore becomes optional
};
Aside from the documentation on mapped types, I once again found this helpful article.
Fun fact: The Optional
Success story sharing
extends keyof
. They really should have chosen a different keyword for this situation. This is really confusion and counter-ituitive. From the doc:T extends Lengthwise
andK extends keyof T
where theextends
keyword means something completely different.extends
is always used for generic,in
is always for the index assessor?A extends B
means "someA
that is a subtype ofB
" for both. IfB
is a keyof type, thenA
is one string from the set --- and the type checker can say more about code that uses this one string thanks to indexed access types.