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
, in this case, is used to constrain the type of a generic parameter. Example:
<T, K extends keyof T>
K
can therefor 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
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<T>
type we've just built has the same signature as the officialPartial<T>
utility type!