After the curly brace in a type definition, what is the significance of [keyof T]?

After reviewing the Typescript documentation found at this specific URL https://www.typescriptlang.org/docs/handbook/advanced-types.html

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

I'm curious about what exactly does that [keyof T] do after the closing brace. Can anyone provide an explanation of this syntax or point me to the relevant documentation?

Answer №1

That's known as a "lookup type".

  1. keyof X retrieves all keys of a specific type

  2. if

    interface sample {
       foo: never
       bar: string
       baz: number
    }

then type not_here = sample['foo'] will result in never

however, lookup types also allow using keyof Something, so

sample[keyof sample] would give the union of all types of properties in sample, which is never | string | number. In this case, never is excluded by TS automatically, resulting in string | number.

(you can even do sample[keyof anotherType], there's no limitation)

I find that breaking down complex types like this into smaller steps helps in understanding them better, just like I did here:

interface test {
    a: string
    b: () => void
    c: () => void
}

type T_A<T> = {
  [K in keyof T]: T[K]
};

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

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

type a = T_A<test>;
type b = T_B<test>;
type c = T_C<test>;

This approach allows you to see each step involved in achieving the desired outcome, which is "the union of keys that are functions".

Answer №2

keyof T refers to the valid keys for the type T (which you probably already knew). Adding [x] after an interface or union type selects the type of the property/member with the name x. For example (playground link):

interface Example {
    a: number;
    b: string;
}
type E1 = Example["a"];
/*
type E1 = number;
*/

The concept of "lookup types" seems to only be documented in the TypeScript 2.1 release notes here:

keyof and Lookup Types In JavaScript, it is common to have APIs that expect property names as parameters, but expressing the type relationships in those APIs has not been possible.

Index Type Query or keyof introduces indexed type query which yields the permitted property names type for T. A keyof T type is considered a subtype of string.

Example

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string

This leads us to indexed access types, also known as lookup types. They are written exactly like element accesses but are intended as types:

Example

type P1 = Person["name"]; // string
type P2 = Person["name" | "age"]; // string | number
type P3 = string["charAt"]; // (pos: number) => string
type P4 = string[]["push"]; // (...items: string[]) => number
type P5 = string[][0]; // string

This pattern can be used within other parts of the type system to perform type-safe lookups.

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]; // Inferred type is T[K]
}

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
  obj[key] = value;
}

let x = { foo: 10, bar: "hello!" };

let foo = getProperty(x, "foo"); // number
let bar = getProperty(x, "bar"); // string

let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"

setProperty(x, "foo", "string"); // Error!, string expected number

The core aspect of FunctionPropertyNames<T> generates an interface with members typed as never for properties of T that are not function-typed, while keeping the original member type for those that are. For example, as illustrated in the code snippet, by defining:

interface Part {
  id: number;
  name: string;
  subparts: Part[];
  updatePart(newName: string): void;
}

type T1 = FunctionPropertyNames<Part>;

The outcome for T1 will be "updatePart" since that's the only function-typed property of Part. Omitting the [keyof T] section would result in the interface containing the never members instead (playground link):

type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type Example<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}/* Same as above, but without [keyof T] here*/;

interface Part {
  id: number;
  name: string;
  subparts: Part[];
  updatePart(newName: string): void;
}

type T1 = FunctionPropertyNames<Part>;
/*
type T1 = "updatePart"
*/
type E1 = Example<Part>;
/*
type E1 = {
    id: never;
    name: never;
    subparts: never;
    updatePart: "updatePart";
}
*/

The inclusion of [keyof T] ensures that FunctionPropertyNames delivers the names of the function-properties rather than the function-typed properties themselves (along with the never-typed ones).

Answer №3

This [keyof T] represents any attribute within T.

For a clearer understanding, consider the following example:

type AB = {
  aa: number,
  ab: () => number,
  cd: () => number,
  ef: () => number
}

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

/* In this example, the resulting value will be { aa: never, ab: 'ab', cd: 'cd', ef: 'ef' } */
const example: FunctionPropertyKeyToNames<AB> = 

By using [keyof T], the values extracted would be
Theoretically never, 'ab', 'cd', 'ef', however since Typescript does not treat never as a valid value
Only 'ab', 'cd', 'ef' will remain.

Answer №4

If the [keyof T] is absent, an object will be returned in brief. Alternatively, if there exists a [keyof T], the key of the preceding object will be returned instead.

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

Tips for customizing the legend color in Angular-chart.js

In the angular-chart.js documentation, there is a pie/polar chart example with a colored legend in the very last section. While this seems like the solution I need, I encountered an issue: My frontend code mirrors the code from the documentation: <can ...

Make sure to call the loader function in React Router only when there are path params present

I'm currently implementing the new React Router, utilizing loader functions to fetch data based on the loaded element. My goal is to have certain APIs called regardless of the route, with additional APIs triggered for specific routes. However, I&apos ...

Convert to a TypeScript map

Here are the configurations I am working with: type Choice = { value: any, label: any } Additionally, there is a role interface: export interface UserRole { id: number name: string } I have a set of roles: const userRoles:UserRole[]:[ {id:1,name: ...

Using the same component multiple times within a parent component in Angular 2

I have a CarsComponent where I repeatedly use the ChartComponent in its template, as shown in the code snippet below: cars.component.html: <div class="row" *ngIf="selectedItemId"> <div class="col-12 mb-2&quo ...

I seem to be missing something: Unhandled Rejection (TypeError): setToken function is not recognized

I am a beginner in React and TypeScript and I am facing an issue while trying to implement a basic functionality. Unfortunately, I keep encountering the error: Unhandled Rejection (TypeError): setToken is not a function. Can anyone provide me with some gui ...

Seeking clarification on how the Typescript/Javascript and MobX code operates

The code provided below was utilized in order to generate an array consisting of object groups grouped by date. While I grasped the overall purpose of the code, I struggled with understanding its functionality. This particular code snippet is sourced from ...

Balancing website speed with capturing product impression

I've been tasked with capturing impressions of all the products visible in the viewport on a website that features thousands of products. To achieve this, I implemented a directory and utilized the IntersectionObserver, which was referenced within the ...

Expanding the properties of an object dynamically and 'directly' by utilizing `this` in JavaScript/TypeScript

Is it possible to directly add properties from an object "directly" to this of a class in JavaScript/TypeScript, bypassing the need to loop through the object properties and create them manually? I have attempted something like this but it doesn't se ...

Showing a whole number formatted with exactly three decimal places in Angular

I am working on an Angular project that includes an input field for users to enter numbers. My goal is to show the number with exactly 3 decimal places if the user submits a whole number. For instance, if the user inputs 6, I want it to be displayed as 6.0 ...

Rearrange the provided string in a particular manner

Looking to transform a string like '12:13:45.123 UTC Sun Oct 17 2021' into 'Sun Oct 17 2021 12:13:45.123 UTC' without calling slice twice. Is there a more elegant and efficient way to achieve this? Currently using: str.slice(18)+&apo ...

The field '_id' is not present in the type Pick

I'm working on a project using Next.js and attempting to query MongoDB with TypeScript and mongoose, but I keep encountering a type error. types.d.ts type dbPost = { _id: string user: { uid: string name: string avatar: string } po ...

Implementing angular-material-design into an Angular 4 application with the help of Angular CLI

Is there a proper way to use bootstrap-material-design in an Angular 4 app with Angular CLI? In my project, I included the css file in my styles.css like this: @import "~bootstrap-material-design/dist/css/bootstrap-material-design.min.css"; Although I c ...

Can this function be rewritten in a manner that does not involve returning undefined?

Using angular fire, I am fetching data from firestore based on the logged-in user. After ensuring that the user object has been retrieved, I have a command to monitor changes in the document. async fetchUserCards() { let _user: UserModel = await this.aut ...

A simple guide on how to send an Angular component to the Nebular dialog service

My current project involves creating a web dialog in Angular6 using Nebular components. Initially, I used the method of passing an <ng-template> reference in the following manner: openAddDialog = (dialogTemplate: TemplateRef<any>) => { ...

Creating Pie Charts with Chart.js using an array of objects as the dataset

I'm working with Chart.js and have my chart data formatted like this: chartData = [ { data: 2, label: 'Label 1' }, { data: 10, label: 'Label 2' }, { data: 40, label: 'Label 3' }, ]; I want to create a classic p ...

pay attention to fluctuations in the observable's value

I am currently working on utilizing Ionic's loadingcontroller alongside a firestore query. From my understanding, this query returns an observable and also monitors changes in the query's value. However, is there a way to determine within the fu ...

Issue regarding locating Material-UI components while resolving TypeScript modules

I am encountering an issue with my Visual Studio 2019 (v16.3.2) React TypeScript project that involves Material-UI components. The TypeScript compiler is unable to resolve any of the @material-ui imports, resulting in errors like the one below which preven ...

Ways to get into the Directive class

@Directive({ selector: '[myHighlight]' }) export class HighlightDirective { static test: number = 5; constructor(private el: ElementRef) { } highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; } } In re ...

What is the best way to incorporate modules into the client side of TypeScript projects?

I'm currently developing a TypeScript project for client-side JavaScript code. Prior to using TypeScript, I used to import a module in vanilla ES6 JavaScript like this: import * as THREE from 'https://threejs.org/build/three.module.js'; H ...

Expanding upon React Abstract Component using Typescript

Currently, I am in the process of building a library that contains presentations using React. To ensure consistency and structure, each presentation component needs to have specific attributes set. This led me to create a TypeScript file that can be extend ...