What causes the return value of keyof to vary in this particular case?

type AppleNode = {
  type: 'Apple'
  name: string
  score: number
}

type BananaNode = {
  type: 'Banana'
  id: number
  score: number
}
type FruitNodes = AppleNode | BananaNode

type fruitTest = {
  [P in keyof FruitNodes]: 21
}
// This will result in:
type fruitTest = {
    type: 21;
    score: 21;
}

However, using generics gives a different outcome

type genericTest<T> = {
  [P in keyof T]: 21
}
type genericTest2 = genericTest<FruitNodes>

genericTest2 now looks like this: type genericTest2 = genericTest | genericTest

View on playground

Answer №1

There is some awareness among people about conditional types distributing over unions, but what is lesser known is that mapped types also exhibit distributive behavior in specific scenarios.

Directly utilizing Nodes does not result in distribution. For example:

type foo3 = {
  [P in keyof Nodes]: 'foo'
}
// foo3 is equivalent to 
// type foo3 = {
//     type: "foo";
//     flag: "foo";
}

This happens because keyof Nodes only captures the common properties of the union, leading to a type with shared properties from both union constituents.

When a mapped type operates on keyof T and T is a type parameter instantiated with a union, the distributive nature of mapped types emerges. This means the mapped type is applied individually to each element of the union, which are then united to create the final output. Essentially,

foo<Nodes> = foo<NodeA | NodeB> = foo<NodeA> | foo<NodeB>
.

This behavior is logical for many types and usually easy to grasp. Mapped types like Readonly<T> demonstrate distributiveness; when applied to a union, it results in a readonly union rather than just the overlapping properties.

If you wish to make a simple type distributive, you can introduce a type parameter through a conditional type:

// Distributive 
type foo3 = Nodes extends infer T ? {
//    ^?
  [P in keyof T]: 'foo'
}: never

Alternatively, to avoid distribution, you can map over something other than keyof T:

// Non-distributive
type foo<T> = {
  [P in Exclude<keyof T, never>]: 'foo'
}

Playground Link

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Managing conflicting versions of React in a component library created with Webpack and Storybook

My goal is to create a React component library on top of MUI using Storybook and TypeScript. Since Storybook is based on Webpack (which includes SASS files), I'm utilizing Webpack to build the bundle because TSC can't compile those files. Subsequ ...

Unable to assign a value to the HTMLInputElement's property: The input field can only be set to a filename or an empty string programmatically

When attempting to upload an image, I encountered the error message listed in the question title: This is my template <input type="file" formControlName="avatar" accept=".jpg, .jpeg .svg" #fileInput (change)="uploa ...

What sets apart extending and intersecting interfaces in TypeScript?

If we have the following type defined: interface Shape { color: string; } Now, let's explore two different methods to add extra properties to this type: Using Extension interface Square extends Shape { sideLength: number; } Using Intersection ...

Ways to access an observable's value without causing a new emit event

Is there a way to retrieve the value of an observable without causing it to emit again? I need this value for an ngClass expression. I attempted to use the tap operator within the pipe to access the values in switchMap, but the value is not being logged. ...

What could be preventing me from setting a boolean variable within an Observable?

After retrieving data from the Service, I am attempting to hide a specific div element. Below is the HTML code: <progressbar *ngIf="isLoadingBootStockData" [value]="100" type="default"> </progressba ...

To subscribe to the display of [Object Object], you cannot use an *ngIf statement without being inside an *ngFor loop

In my angular Quiz project, I have a functionality where every user can create quizzes. I want to display all the quizzes that a logged-in user has completed. Here is how I attempted to achieve this: // Retrieving user ID and category ID inside Re ...

Rule in Eslint for Typescript that enforces explicit typing and prohibits the use of implicit "any

Starting a fresh typescript project with eslint, I'm facing an issue in setting up eslint rules for the tsc command to run smoothly without errors. Specifically, I'm encountering difficulty with the "noImplicitAny" rule defined in tsconfig.json, ...

Obtain the promise value before returning the observable

I'm facing an issue with the code below, as it's not working properly. I want to return an observable once the promise is resolved. Any thoughts or suggestions would be greatly appreciated. getParalelos() { let _observable; this.getTo ...

Is it possible to import both type and value on the same line when isolatedModules=true?

Did you know with Typescript, you can do type-only imports? import type { Foo } from "./types" If the file exports both types and values, you can use two separate import statements like this: import type { Foo } from "./types"; import ...

Exploring the possibilities of integrating jQuery into Angular 2 using Javascript

import {Component, ElementRef, OnInit} from 'angular2/core'; declare var jQuery:any; @Component({ selector: 'jquery-integration', templateUrl: './components/jquery-integration/jquery-integration.html' } ...

Tips for efficiently verifying existence of object attribute in conditional object type using Typescript

Below is a simplified version of the code I am working with, showcasing a type that can be an interface or another: interface ChatBase { roomId?: string type: "message" | "emoji" configs: unknown } interface ChatMessage exte ...

Unable to employ the .some() method with an array consisting of objects

I am currently attempting to determine whether my array of objects contains an attribute with a specific value. I wanted to use the array.some() method instead of a foreach loop, but I keep encountering an error: Error TS2345: Argument of type '(ele ...

Form with Material-UI's FreeSolo AutoComplete feature

Visit this Sandbox for more details In the provided SandBox example, Material AutoComplete is being used as a multiple input with free options. The component is expected to return ["term1","term2","term3"] to Formik, and each string should be displayed as ...

Puzzled by the specialized link feature

As I delve into the world of React and Next.js, I find myself working on the link component. Initially, I had a grasp on basic routing in next.js which seemed pretty straightforward. However, things took a confusing turn when I stumbled upon this code: imp ...

Best practice for importing ts files from an npm package

When faced with the need to divide a ts project into multiple repositories/packages for creating microservices, the challenge arises in combining these packages efficiently. Some packages are required in one microservice, others in another, and some in all ...

The VueJS function is not defined

Looking for a way to fetch data from graphql in my vue project and store it in a variable. The function is asynchronous and the value of rawID needs to be awaited. However, there is a possibility that it could result in undefined, causing an error in the ...

Having trouble retrieving image information within the Asp.net core controller

I am facing an issue trying to store image details in the database through Angular and ASP.NET Core. I am unable to retrieve the image data sent from Angular in the controller. Although I am able to obtain the image information using the [FromForm] attribu ...

Issue with closing the active modal in Angular TypeScript

Can you help me fix the issue with closing the modal window? I have written a function, but the button does not respond when clicked. Close Button: <button type="submit" (click)="activeModal.close()" ...

New Authentication: The sign-in feature will redirect to /api/auth/error

Currently implementing Google sign-in functionality on my Next.js 13 app using Next-auth. I have utilized the signIn() function as described in the documentation here. However, upon calling the signIn() function, I am unexpectedly redirected to http://loca ...

Running the NPM build command results in an error specifically related to an HTML file

I encountered an issue in my AngularJS application when running the command: npm run build -- -prod The error message I received was: ERROR in ng:///home/directoryling/appname-play.component.html (173,41): The left-hand side of an arithmetic operation ...