type NodeA = {
type: 'A'
name: string
flag: number
}
type NodeB = {
type: 'B'
id: number
flag: number
}
type Nodes = NodeA | NodeB
type test = {
[P in keyof Nodes]: 21
}
// it return this
type test = {
type: 21;
flag: 21;
}
But when I use the generic,the results are completely different
type test<T> = {
[P in keyof T]: 21
}
type test2 = test<Nodes>
test2 return like this type test2 = test | test
CodePudding user response:
People are somewhat aware of the fact that some conditional types distribute over unions, but what is even less documented is that mapped type are also distributive in certain scenarios.
When you use Nodes
directly there is no distribution. So we have
type foo3 = {
[P in keyof Nodes]: 'foo'
}
// foo3 is equivalent to
// type foo3 = {
// type: "foo";
// flag: "foo";
// }
This is beacuse keyof Nodes
will only result in the common properties of the union so we get a type that only contains properties that are present in both union constituents.
When you have a mapped type that maps over keyof T
, if T
is a type parameter that is instantiated with a union, then we get the distributive behavior of mapped types. This means the mapped typed is applied to each constituent of the union and then the results are unioned to form the final result. So we basically have foo<Nodes> = foo<NodeA | NodeB> = foo<NodeA> | foo<NodeB>
.
This behavior makes sense for a lot of types and is usually pretty intuitive. Readonly<T>
which is a mapped type is distributive so if you apply to a union, you get a readonly union, not a mangled mess of just the common properties.
You can make a simple type distributive by introducing a type parameter using a conditional type:
// Distributive
type foo3 = Nodes extends infer T ? {
// ^?
[P in keyof T]: 'foo'
}: never
You can also avoid distribution by mapping over something other than keyof T
:
// Non distributive
type foo<T> = {
[P in Exclude<keyof T, never>]: 'foo'
}