154

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?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Golo Roden
  • 140,679
  • 96
  • 298
  • 425

1 Answers1

274

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<T> type we've just built has the same signature as the official Partial<T> utility type!

Bob Horn
  • 33,387
  • 34
  • 113
  • 219
Tobias Tengler
  • 6,848
  • 4
  • 20
  • 34
  • 22
    Thanks for the answer. I was stuck on the concept of `extends keyof`. They really should have chosen a different keyword for this situation. This is really confusion and counter-ituitive. From the [doc](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints): `T extends Lengthwise` and `K extends keyof T` where the `extends` keyword means something completely different. – cbdeveloper Aug 13 '20 at 19:00
  • 3
    Does it mean, the `extends` is always used for generic, `in` is always for the index assessor? – Junle Li Aug 28 '20 at 20:59
  • 3
    @JunleLi yes, you can't use them the other way around. Sorry for the late response, I seem to have missed your comment! – Tobias Tengler Nov 07 '20 at 23:03
  • 1
    @cbdeveloper `A extends B` means "some `A` that is a subtype of `B`" for both. If `B` is a keyof type, then `A` 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. – Ben Greenman Jan 07 '21 at 14:34
  • Hi, I've got some trouble with this typing: `sort: { [fieldName in keyof TModel]?: SortDefinition }`. I need the `?:` so that I can define sort only for some keys of `TModel`. But the problem is that the type is `SortDefinition | undefined`, and not `SortDefinition`, for keys that are actually used. Should I use `extends` in this scenario? – Eric Burel Apr 07 '21 at 10:00
  • 3
    Much clearer explanation than given by the otherwise pretty awesome official handbook. – ChrisCrossCrash Apr 20 '21 at 17:46
  • Well done, great response. Your quote at the end there, "Fun fact: The Optional type we've just built has the same signature as the official Partial utility type!". Is what I was thinking from the beginning. It seems, at times, TypeScript allows you to do things in a very similar fashion. To the point where it's difficult to understand why you'd do this or that. In this case using Optional instead of Partial, using keyof instead of just doing a partial or new type. Thanks for your response. – Dylan Wright May 07 '21 at 20:08
  • @EricBurel "fieldName in keyof TModel" defines the condition, in which the mapped type "SortDefinition" can be assumed by TS. Without the question mark (?), a mismatch would result in an error. But with the question mark, the type of the value is just unknown. I guess TS just checks what types it can eliminate and if it cannot eliminate the chance that it is unknown, it will keep that option in the union type. – Michael S. May 05 '22 at 21:55
  • If u still don't understand what it means like me, please have a look at this link, it give much clearer explanation => https://blog.devgenius.io/how-to-use-the-keyof-type-operator-in-typescript-6d5e0ea6740f – Sam Kah Chiin Jul 24 '22 at 04:53
  • @cbdeveloper you are right, some things in TypeScript are counterintuitive. I also don't like it that they made types and interfaces. Just types would be enough. – Robo Robok Jan 31 '23 at 00:53