Home > Enterprise >  Why does keyof return a different result in this example
Why does keyof return a different result in this example

Time:03-01

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

playground link

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'
}

Playground Link

  • Related