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.
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).
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With