Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does T[keyof T] work like the distributive conditional type?

I'm trying to understand this example from the Distributive conditional types section:

Conditional types are particularly useful when combined with mapped types:

type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

The usage of the index access operator is not clear for me, in this example. Here is the simplest example which uses the same idea (as far as I can understand):

type Foo = {
    prop1: never;
    prop2: never
    method1: "method1";
    method2: "method2";
}

type FunctionPropertyNames = Foo[keyof Foo] // "method1" | "method2"

So, my question: is such usage of index access operator just a specific case of the distributive conditional type? Because it looks like result type is the union of all applications of T[K] where never is filtered because "never is the empty union"


Update. I will try to formulate this question more precisely: TS Handbook describes what is the syntax for mapped types, and for conditional types. However, I did not find any description of this form of the type definition:

type Keys = 'name1' | 'name2' | 'name3'
type Foo = Bar[Keys]

or, more realistic:

type Foo = Bar[keyof Bar]

It looks like some kind of mapping, but it also provides filtering, in the case when Keys contains never. So my question was more about if there any precise description of this feature of the TS type system.

like image 919
azaviruha Avatar asked Nov 07 '22 05:11

azaviruha


1 Answers

This is a specific usage that is powerful when it comes to filter the properties of an object/class based on the type of the attributes. This article explains it really well how it is convenient to filter a union of classes based on a method they implement or not (see section Refining unions with distributive conditional types of the article).

Other usage

As you said, the advantage of this usage of index access operator helps to remove irrevlevant types from a union but it has other advantages like to change a type based on certain conditions.

Here is an example:

type SchemaType<T> = {
  [k in keyof T]: T[k] extends
    | string
    | number
    | boolean
    | Types.ObjectId
    | Client
    | Doer
    | Mission
    | Date
    | Array<any>
    ? SchemaDefinition['']
    : SchemaType<T[k]>;
};

This is a type I use to type my mongoose documents.

I have a typescript interface that defines what a User is and a document that describes the document in mongoDb.

What I wanted is to have the document implement the same hierarchy as the interface.

For example, if the User has the structure:

interface User {
  id: Types.ObjectId;
  profile: {
    first_name: string;
  }
}

I wanted the document to enforce the same one and be typesafe:

const schema: SchemaType<User> = {
  id: { type: Types.ObjectId },
  profile: {
    first_name: { type: String, default: '' },
  },
};

That's what does the SchemaType with the index access operator. If the property is considered a leaf type (like id and first_name are), then it must be typed as a SchemaDefinition in the document. If it's an object, then it must be typed as it is in the interface (that's what SchemaType<T[k]> does).

like image 104
Baboo Avatar answered Nov 14 '22 23:11

Baboo