Reputation: 36310
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
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
Reputation: 10163
That's called a "lookup type".
keyof X
returns all keys of a type
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 a
s 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
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
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 querykeyof T
yields the type of permitted property names for T. Akeyof 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