Dmitri
Dmitri

Reputation: 36310

What does [keyof T] mean after the closing brace in type definition?

Looking at the Typescript documentation at this 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];

Can someone explain to me what this [keyof T] after the closing brace mean? Is there any documentation for this syntax?

Upvotes: 4

Views: 2948

Answers (4)

Ha0ran
Ha0ran

Reputation: 699

In a short, if there is no [keyof T], it would return an object. However, If there is a [keyof T], it would return the key of the object before.

Upvotes: 0

Sergiu Paraschiv
Sergiu Paraschiv

Reputation: 10163

That's called a "lookup type".

  1. keyof X returns all keys of a type

  2. if

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

then type not_here = a['foo'] will be never

but lookup types also support passing keyof Something in, so

a[keyof a] would be the union of all types of as keys/properties, which is never | string | number. Though never has no meaning in there so TS automatically ignores it, resulting in string | number.

(you can of course do a[keyof b], there's no restriction here)

I find that the best way to figure out complex types like this one is to decompose them in steps 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<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>

With this you can see the individual steps taken to obtain the desired result, which is "the union of keys which are of type Function".

Upvotes: 4

Felipe Malara
Felipe Malara

Reputation: 1914

This [keyof T] represents any attribute within T.

To better understand, take the following:

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

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

/* The only possible value for this 👇 example will be { aa: never, ab: 'ab', cd: 'cd', ef: 'ef' } */
const example: FunctionPropertyKeyToNames<AB> = 

Now, using [keyof T] we'd get only the values,
In theory never, 'ab', 'cd', 'ef', but as Typescript won't resolve never as a value for you
You'd end up only with 'ab', 'cd', 'ef'

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1075149

keyof T means valid keys for the T type (you probably knew that). When you have [x] after an interface or union type, it picks 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;
*/

As far as I know, these "lookup types" are only documented in the TypeScript 2.1 release notes here:

keyof and Lookup Types In JavaScript it is fairly common to have APIs that expect property names as parameters, but so far it hasn’t been possible to express the type relationships that occur in those APIs.

Enter Index Type Query or keyof; An indexed type query keyof T yields the type of permitted property names 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

The dual of this is indexed access types, also called lookup types. Syntactically, they look exactly like an element access, but are written 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

You can use this pattern with other parts of the type system to get 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 main part of FunctionPropertyNames<T> produces an interface with never-typed members for all the properties of T that aren't function-typed, and the original member type for those that are. For instance, as shown in the example, if you do this:

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

type T1 = FunctionPropertyNames<Part>;

T1 ends up being "updatePart" because that's the only key of T (Part) that has a function type. Without the [keyof T] part, you'd get the interface with 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";
}
*/

It's the [keyof T] part that makes FunctionPropertyNames provide the names of the function-typed properties, rather than the function-typed properties themselves (and never-typed ones).

Upvotes: 1

Related Questions