From 866bb4f7049cccac2bf1ce8ce03ea6b02caabb28 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 29 Nov 2016 15:48:53 -0800 Subject: [PATCH 1/7] Document index and indexed access types --- pages/Advanced Types.md | 80 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index cd2042218..9d749a8f7 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -283,6 +283,86 @@ The right side of the `instanceof` needs to be a constructor function, and TypeS in that order. +# Index and index access types + +With index and index access types, you can get the compiler to check code that uses dynamic property names. +For example, a common Javascript pattern is to pick a certain set of properties from an object: + +```js +function pluck(o, names) { + return names.map(n => o[n]); +} +``` + +Here's how you would type this function: + +```ts +function pluck(o: T, names: K[]): T[K][] { + return names.map(n => o[n]); +} +``` + +That example introduces several new type operators. +Let's look at them one by one. +First is `keyof T`, the index operator. +For any type `T`, `keyof T` is the union of property names of `T`. +For example: + +```ts +interface Person { + name: string; + age: number; +} +let keys: keyof Person; // 'name' | 'age' +``` + +`keyof Person` is completely interchangeable with `'name | 'age`. +The difference is that if you add another property to `Person`, say `address: string`, then `keyof Person` will automatically update to be `'name' | 'age' | 'address'`. +And you can use `keyof` in generic contexts like `pluck`, where you can't possibly know the property names ahead of time. +That means the compiler will check that you pass the right set of property names to `pluck`: + +```ts +let person: Person; +let result = pluck(person, ['name']); // ok +let result = pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age' +``` + +The second operator is `T[K]`, the indexed access operator. +Here, the type syntax reflects the expression syntax. +That means that `person['name']` has the type `Person['name']` — which in our example is just `string`. +Again, you can use `T[K]` in a generic context, which is where its real power comes to life. +You just have to make sure that the type variable `K extends keyof T`. +Here's another example with a function named `getProperty`. + +```ts +function getProperty(o: T, name: K): K[T] { + return o[name]; // o[name] is of type T[K] +} +``` + +In `getProperty`, `o: T` and `name: K`, so that means `o[name]: T[K]`. +Once you return the T[K] result, the compiler will instantiate the actual type of the key, so the return type of `getProperty` will vary according to which property you request. + +```ts +let name: string = getProperty(person, 'name'); +let age: number = getProperty(person, 'age'); +let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age' +``` + +## Index types and string index signatures + +`keyof` and `T[K]` interact with string index signatures. +If you have a type with a string index signature, `keyof T` will just be `string`. +And `T[string]` is just the type of the index signature: + +```ts +interface Map { + [key: string]: T; +} +let keys: keyof Map; // string +let value: Map['foo']; // number +``` + # Type Aliases Type aliases create a new name for a type. From 91d8fd3b7aaa125e515b5f1c03c92b6a3c2b0e3f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 29 Nov 2016 16:51:50 -0800 Subject: [PATCH 2/7] Document mapped types --- pages/Advanced Types.md | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index 9d749a8f7..edb37fe71 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -363,6 +363,64 @@ let keys: keyof Map; // string let value: Map['foo']; // number ``` +# Mapped types + +Mapped types allow you to create new types based on old types. +For example, you can make all fields of a type `readonly` or optional. +Here's how you do it: + +```ts +type Readonly = { + readonly [P in keyof T]: T[P]; +} +type Partial = { + [P in keyof T]?: T[P]; +} +``` + +And to use it: + +```ts +interface Props { + name: string; + age: number; +} +type UpdateProps = Partial; +``` + +The simplest form of a mapped type is `{ [P in 'option1' | 'option2']: boolean }`. +The syntax resembles the syntax for index signatures. +`P` is each property in the list of properties `'option1' | 'option2'`, and `boolean` is the resulting type. +This example just makes a type with two properties, both of type `boolean`. + +You can also add various modifiers like `?` and `readonly`. +Or you can wrap the resulting properties to make a proxied type: + +```ts +type Proxy = { + get(): T; + set(value: T): void; +} +type Proxify = { + [P in keyof T]: Proxy; +} +function proxify(o: T): Proxify { + // ... wrap proxies ... +} +let proxyProps = proxify(props); +``` + +Note that `Readonly` and `Partial` are so useful, they are included in TypeScript's standard libarary along with `Pick` and `Record`: + +```ts +type Pick = { + [P in K]: T[P]; +} +type Record = { + [P in K]: T; +} +``` + # Type Aliases Type aliases create a new name for a type. From c9143a82f1f86257cbd6528d15cd7f9d05e7b0da Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 30 Nov 2016 13:46:07 -0800 Subject: [PATCH 3/7] Reword and reorder index types discussion --- pages/Advanced Types.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index edb37fe71..8d4e8456f 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -283,10 +283,10 @@ The right side of the `instanceof` needs to be a constructor function, and TypeS in that order. -# Index and index access types +# Index types -With index and index access types, you can get the compiler to check code that uses dynamic property names. -For example, a common Javascript pattern is to pick a certain set of properties from an object: +With index types, you can get the compiler to check code that uses dynamic property names. +For example, a common Javascript pattern is to pick a subset of properties from an object: ```js function pluck(o, names) { @@ -294,26 +294,30 @@ function pluck(o, names) { } ``` -Here's how you would type this function: +Here's how you would write and use this function in TypeScript: ```ts function pluck(o: T, names: K[]): T[K][] { return names.map(n => o[n]); } + +interface Person { + name: string; + age: number; +} +let person: Person; +let strings: string[] = pluck(person, ['name']); // ok, string[] ``` -That example introduces several new type operators. -Let's look at them one by one. -First is `keyof T`, the index operator. +The compiler checks that `name` is actually a property on `Person`, and it knows that `strings` is a `string[]` because `name` is a `string`. +To make this work, the example introduces a couple of new type operators. +First is `keyof T`, the index type query operator. For any type `T`, `keyof T` is the union of property names of `T`. +The syntax is similar to `typeof` except that it works on types instead of values. For example: ```ts -interface Person { - name: string; - age: number; -} -let keys: keyof Person; // 'name' | 'age' +let personProps: keyof Person; // 'name' | 'age' ``` `keyof Person` is completely interchangeable with `'name | 'age`. @@ -322,15 +326,13 @@ And you can use `keyof` in generic contexts like `pluck`, where you can't possib That means the compiler will check that you pass the right set of property names to `pluck`: ```ts -let person: Person; -let result = pluck(person, ['name']); // ok -let result = pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age' +pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age' ``` The second operator is `T[K]`, the indexed access operator. Here, the type syntax reflects the expression syntax. That means that `person['name']` has the type `Person['name']` — which in our example is just `string`. -Again, you can use `T[K]` in a generic context, which is where its real power comes to life. +However, just like index type queries, you can use `T[K]` in a generic context, which is where its real power comes to life. You just have to make sure that the type variable `K extends keyof T`. Here's another example with a function named `getProperty`. From 4c14a03d004918f4cd78892e69407504135382ab Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 5 Dec 2016 11:33:08 -0800 Subject: [PATCH 4/7] Reorder mapped types example Based on the more involved discussion from the blog post. --- pages/Advanced Types.md | 77 ++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index 8d4e8456f..b4c70f399 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -367,9 +367,28 @@ let value: Map['foo']; // number # Mapped types -Mapped types allow you to create new types based on old types. -For example, you can make all fields of a type `readonly` or optional. -Here's how you do it: +A common task is to take an existing type and make each of its properties optional: + +```ts +interface PersonPartial { + name?: string; + age?: number; +} +``` + +Or we might want a readonly version: + +```ts +interface PersonReadonly { + readonly name: string; + readonly age: number; +} +``` + +This happens often enough in Javascript that TypeScript provides a way to create new types based on old types — mapped types. +In a mapped type, the new type transforms each property in the old type in the same way. +For example, you can make all properties of a type `readonly` or optional. +Here are a couple of examples: ```ts type Readonly = { @@ -383,20 +402,52 @@ type Partial = { And to use it: ```ts -interface Props { - name: string; - age: number; +type PersonPartial = Partial; +type ReadonlyPerson = Readonly; +``` + +Let's take a look at the simplest mapped type and its parts: + +```ts +type Properties = 'option1' | 'option2'; +type Flags = { [P in Properties]: boolean }; +``` + +The syntax resembles the syntax for index signatures with a `for .. in` inside. +There are three parts: + +1. The type variable `P`, which gets bound to each property in turn. +2. The string literal union `Properties`, which contains the names of properties to iterate over. +3. The resulting type of the property. + +In this simple example, `Properties` is a hard-coded list of property names and the property type is always `boolean`, so this mapped type is equivalent to writing: + +```ts +type Flags = { + option1: boolean; + option2: boolean; } -type UpdateProps = Partial; ``` -The simplest form of a mapped type is `{ [P in 'option1' | 'option2']: boolean }`. -The syntax resembles the syntax for index signatures. -`P` is each property in the list of properties `'option1' | 'option2'`, and `boolean` is the resulting type. -This example just makes a type with two properties, both of type `boolean`. +Real applications, however, look like `Readonly` or `Partial` above. +They're based on some existing type, and they transform the fields in some way. +That's where `keyof` and indexed access types come in: + +```ts +type NullablePerson = { [P in keyof Person]: Person[P] | null } +type PartialPerson = { [P in keyof Person]?: Person[P] } +``` + +But it's more useful to have a general version. + +```ts +type Nullable = { [P in keyof T]: T[P] | null } +type Partial = { [P in keyof T]?: T[P] } +``` -You can also add various modifiers like `?` and `readonly`. -Or you can wrap the resulting properties to make a proxied type: +In these examples, the properties list is `keyof T` and the resulting type is some variant of `T[P]`. +This is a good template for any general use of mapped types. +Here's one more example, in which `T[P]` is wrapped in a `Proxy` class: ```ts type Proxy = { From bc3f9e393f7e39b95a7a36c6b1cb0fed29900edc Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 5 Dec 2016 13:01:24 -0800 Subject: [PATCH 5/7] Minor edits from PR comments --- pages/Advanced Types.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index b4c70f399..ebe2c7250 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -312,8 +312,7 @@ let strings: string[] = pluck(person, ['name']); // ok, string[] The compiler checks that `name` is actually a property on `Person`, and it knows that `strings` is a `string[]` because `name` is a `string`. To make this work, the example introduces a couple of new type operators. First is `keyof T`, the index type query operator. -For any type `T`, `keyof T` is the union of property names of `T`. -The syntax is similar to `typeof` except that it works on types instead of values. +For any type `T`, `keyof T` is the union of known, public property names of `T`. For example: ```ts @@ -337,7 +336,7 @@ You just have to make sure that the type variable `K extends keyof T`. Here's another example with a function named `getProperty`. ```ts -function getProperty(o: T, name: K): K[T] { +function getProperty(o: T, name: K): T[K] { return o[name]; // o[name] is of type T[K] } ``` @@ -409,18 +408,18 @@ type ReadonlyPerson = Readonly; Let's take a look at the simplest mapped type and its parts: ```ts -type Properties = 'option1' | 'option2'; -type Flags = { [P in Properties]: boolean }; +type Keys = 'option1' | 'option2'; +type Flags = { [K in Keys]: boolean }; ``` The syntax resembles the syntax for index signatures with a `for .. in` inside. There are three parts: -1. The type variable `P`, which gets bound to each property in turn. -2. The string literal union `Properties`, which contains the names of properties to iterate over. +1. The type variable `K`, which gets bound to each property in turn. +2. The string literal union `Keys`, which contains the names of properties to iterate over. 3. The resulting type of the property. -In this simple example, `Properties` is a hard-coded list of property names and the property type is always `boolean`, so this mapped type is equivalent to writing: +In this simple example, `Keys` is a hard-coded list of property names and the property type is always `boolean`, so this mapped type is equivalent to writing: ```ts type Flags = { From 22892f1ce3a4c355bd38f3c10dcc358bbe3b083b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 5 Dec 2016 14:49:38 -0800 Subject: [PATCH 6/7] Discuss mapped type inference and call out terms better --- pages/Advanced Types.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index ebe2c7250..c5586c78e 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -294,7 +294,7 @@ function pluck(o, names) { } ``` -Here's how you would write and use this function in TypeScript: +Here's how you would write and use this function in TypeScript, using the **index type query** and **indexed access** operators: ```ts function pluck(o: T, names: K[]): T[K][] { @@ -311,7 +311,7 @@ let strings: string[] = pluck(person, ['name']); // ok, string[] The compiler checks that `name` is actually a property on `Person`, and it knows that `strings` is a `string[]` because `name` is a `string`. To make this work, the example introduces a couple of new type operators. -First is `keyof T`, the index type query operator. +First is `keyof T`, the **index type query operator**. For any type `T`, `keyof T` is the union of known, public property names of `T`. For example: @@ -328,7 +328,7 @@ That means the compiler will check that you pass the right set of property names pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age' ``` -The second operator is `T[K]`, the indexed access operator. +The second operator is `T[K]`, the **indexed access operator**. Here, the type syntax reflects the expression syntax. That means that `person['name']` has the type `Person['name']` — which in our example is just `string`. However, just like index type queries, you can use `T[K]` in a generic context, which is where its real power comes to life. @@ -384,7 +384,7 @@ interface PersonReadonly { } ``` -This happens often enough in Javascript that TypeScript provides a way to create new types based on old types — mapped types. +This happens often enough in Javascript that TypeScript provides a way to create new types based on old types — **mapped types**. In a mapped type, the new type transforms each property in the old type in the same way. For example, you can make all properties of a type `readonly` or optional. Here are a couple of examples: @@ -473,6 +473,29 @@ type Record = { } ``` +## Inference from mapped types + +Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them. +Fortunately, that's pretty easy: + +```ts +function unproxify(t: Proxify): T { + let result = {} as T; + for (const k in t) { + result[k] = t[k].get(); + } + return result; +} + +let originalProps = unproxify(proxyProps); +``` + +Note that this unwrapping inference works best on *homomorphic* mapped types. +Homomorphic mapped types are mapped types that iterate over every property of some type, and only those properties: `{ [P in keyof T]: X }`. +In the examples above, `Nullable` and `Partial` are homomorphic whereas `Pick` and `Record` are not. +One clue is that `Pick` and `Record` both take a union of property names in addition to a source type, which they use instead of `keyof T`. +If the mapped type is not homomorphic you might have to explicitly give a type parameter to your unwrapping function. + # Type Aliases Type aliases create a new name for a type. From 6a9e4b54765282135927e8b877b585d52c05812a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 5 Dec 2016 14:59:31 -0800 Subject: [PATCH 7/7] Move index and map types to end of Advanced Types The feature depends on type aliases and string literal types, so should come after those. --- pages/Advanced Types.md | 426 ++++++++++++++++++++-------------------- 1 file changed, 213 insertions(+), 213 deletions(-) diff --git a/pages/Advanced Types.md b/pages/Advanced Types.md index c5586c78e..e7f85a68a 100644 --- a/pages/Advanced Types.md +++ b/pages/Advanced Types.md @@ -283,219 +283,6 @@ The right side of the `instanceof` needs to be a constructor function, and TypeS in that order. -# Index types - -With index types, you can get the compiler to check code that uses dynamic property names. -For example, a common Javascript pattern is to pick a subset of properties from an object: - -```js -function pluck(o, names) { - return names.map(n => o[n]); -} -``` - -Here's how you would write and use this function in TypeScript, using the **index type query** and **indexed access** operators: - -```ts -function pluck(o: T, names: K[]): T[K][] { - return names.map(n => o[n]); -} - -interface Person { - name: string; - age: number; -} -let person: Person; -let strings: string[] = pluck(person, ['name']); // ok, string[] -``` - -The compiler checks that `name` is actually a property on `Person`, and it knows that `strings` is a `string[]` because `name` is a `string`. -To make this work, the example introduces a couple of new type operators. -First is `keyof T`, the **index type query operator**. -For any type `T`, `keyof T` is the union of known, public property names of `T`. -For example: - -```ts -let personProps: keyof Person; // 'name' | 'age' -``` - -`keyof Person` is completely interchangeable with `'name | 'age`. -The difference is that if you add another property to `Person`, say `address: string`, then `keyof Person` will automatically update to be `'name' | 'age' | 'address'`. -And you can use `keyof` in generic contexts like `pluck`, where you can't possibly know the property names ahead of time. -That means the compiler will check that you pass the right set of property names to `pluck`: - -```ts -pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age' -``` - -The second operator is `T[K]`, the **indexed access operator**. -Here, the type syntax reflects the expression syntax. -That means that `person['name']` has the type `Person['name']` — which in our example is just `string`. -However, just like index type queries, you can use `T[K]` in a generic context, which is where its real power comes to life. -You just have to make sure that the type variable `K extends keyof T`. -Here's another example with a function named `getProperty`. - -```ts -function getProperty(o: T, name: K): T[K] { - return o[name]; // o[name] is of type T[K] -} -``` - -In `getProperty`, `o: T` and `name: K`, so that means `o[name]: T[K]`. -Once you return the T[K] result, the compiler will instantiate the actual type of the key, so the return type of `getProperty` will vary according to which property you request. - -```ts -let name: string = getProperty(person, 'name'); -let age: number = getProperty(person, 'age'); -let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age' -``` - -## Index types and string index signatures - -`keyof` and `T[K]` interact with string index signatures. -If you have a type with a string index signature, `keyof T` will just be `string`. -And `T[string]` is just the type of the index signature: - -```ts -interface Map { - [key: string]: T; -} -let keys: keyof Map; // string -let value: Map['foo']; // number -``` - -# Mapped types - -A common task is to take an existing type and make each of its properties optional: - -```ts -interface PersonPartial { - name?: string; - age?: number; -} -``` - -Or we might want a readonly version: - -```ts -interface PersonReadonly { - readonly name: string; - readonly age: number; -} -``` - -This happens often enough in Javascript that TypeScript provides a way to create new types based on old types — **mapped types**. -In a mapped type, the new type transforms each property in the old type in the same way. -For example, you can make all properties of a type `readonly` or optional. -Here are a couple of examples: - -```ts -type Readonly = { - readonly [P in keyof T]: T[P]; -} -type Partial = { - [P in keyof T]?: T[P]; -} -``` - -And to use it: - -```ts -type PersonPartial = Partial; -type ReadonlyPerson = Readonly; -``` - -Let's take a look at the simplest mapped type and its parts: - -```ts -type Keys = 'option1' | 'option2'; -type Flags = { [K in Keys]: boolean }; -``` - -The syntax resembles the syntax for index signatures with a `for .. in` inside. -There are three parts: - -1. The type variable `K`, which gets bound to each property in turn. -2. The string literal union `Keys`, which contains the names of properties to iterate over. -3. The resulting type of the property. - -In this simple example, `Keys` is a hard-coded list of property names and the property type is always `boolean`, so this mapped type is equivalent to writing: - -```ts -type Flags = { - option1: boolean; - option2: boolean; -} -``` - -Real applications, however, look like `Readonly` or `Partial` above. -They're based on some existing type, and they transform the fields in some way. -That's where `keyof` and indexed access types come in: - -```ts -type NullablePerson = { [P in keyof Person]: Person[P] | null } -type PartialPerson = { [P in keyof Person]?: Person[P] } -``` - -But it's more useful to have a general version. - -```ts -type Nullable = { [P in keyof T]: T[P] | null } -type Partial = { [P in keyof T]?: T[P] } -``` - -In these examples, the properties list is `keyof T` and the resulting type is some variant of `T[P]`. -This is a good template for any general use of mapped types. -Here's one more example, in which `T[P]` is wrapped in a `Proxy` class: - -```ts -type Proxy = { - get(): T; - set(value: T): void; -} -type Proxify = { - [P in keyof T]: Proxy; -} -function proxify(o: T): Proxify { - // ... wrap proxies ... -} -let proxyProps = proxify(props); -``` - -Note that `Readonly` and `Partial` are so useful, they are included in TypeScript's standard libarary along with `Pick` and `Record`: - -```ts -type Pick = { - [P in K]: T[P]; -} -type Record = { - [P in K]: T; -} -``` - -## Inference from mapped types - -Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them. -Fortunately, that's pretty easy: - -```ts -function unproxify(t: Proxify): T { - let result = {} as T; - for (const k in t) { - result[k] = t[k].get(); - } - return result; -} - -let originalProps = unproxify(proxyProps); -``` - -Note that this unwrapping inference works best on *homomorphic* mapped types. -Homomorphic mapped types are mapped types that iterate over every property of some type, and only those properties: `{ [P in keyof T]: X }`. -In the examples above, `Nullable` and `Partial` are homomorphic whereas `Pick` and `Record` are not. -One clue is that `Pick` and `Record` both take a union of property names in addition to a source type, which they use instead of `keyof T`. -If the mapped type is not homomorphic you might have to explicitly give a type parameter to your unwrapping function. - # Type Aliases Type aliases create a new name for a type. @@ -781,3 +568,216 @@ let v = new ScientificCalculator(2) Without `this` types, `ScientificCalculator` would not have been able to extend `BasicCalculator` and keep the fluent interface. `multiply` would have returned `BasicCalculator`, which doesn't have the `sin` method. However, with `this` types, `multiply` returns `this`, which is `ScientificCalculator` here. + +# Index types + +With index types, you can get the compiler to check code that uses dynamic property names. +For example, a common Javascript pattern is to pick a subset of properties from an object: + +```js +function pluck(o, names) { + return names.map(n => o[n]); +} +``` + +Here's how you would write and use this function in TypeScript, using the **index type query** and **indexed access** operators: + +```ts +function pluck(o: T, names: K[]): T[K][] { + return names.map(n => o[n]); +} + +interface Person { + name: string; + age: number; +} +let person: Person; +let strings: string[] = pluck(person, ['name']); // ok, string[] +``` + +The compiler checks that `name` is actually a property on `Person`, and it knows that `strings` is a `string[]` because `name` is a `string`. +To make this work, the example introduces a couple of new type operators. +First is `keyof T`, the **index type query operator**. +For any type `T`, `keyof T` is the union of known, public property names of `T`. +For example: + +```ts +let personProps: keyof Person; // 'name' | 'age' +``` + +`keyof Person` is completely interchangeable with `'name' | 'age'`. +The difference is that if you add another property to `Person`, say `address: string`, then `keyof Person` will automatically update to be `'name' | 'age' | 'address'`. +And you can use `keyof` in generic contexts like `pluck`, where you can't possibly know the property names ahead of time. +That means the compiler will check that you pass the right set of property names to `pluck`: + +```ts +pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age' +``` + +The second operator is `T[K]`, the **indexed access operator**. +Here, the type syntax reflects the expression syntax. +That means that `person['name']` has the type `Person['name']` — which in our example is just `string`. +However, just like index type queries, you can use `T[K]` in a generic context, which is where its real power comes to life. +You just have to make sure that the type variable `K extends keyof T`. +Here's another example with a function named `getProperty`. + +```ts +function getProperty(o: T, name: K): T[K] { + return o[name]; // o[name] is of type T[K] +} +``` + +In `getProperty`, `o: T` and `name: K`, so that means `o[name]: T[K]`. +Once you return the T[K] result, the compiler will instantiate the actual type of the key, so the return type of `getProperty` will vary according to which property you request. + +```ts +let name: string = getProperty(person, 'name'); +let age: number = getProperty(person, 'age'); +let unknown = getProperty(person, 'unknown'); // error, 'unknown' is not in 'name' | 'age' +``` + +## Index types and string index signatures + +`keyof` and `T[K]` interact with string index signatures. +If you have a type with a string index signature, `keyof T` will just be `string`. +And `T[string]` is just the type of the index signature: + +```ts +interface Map { + [key: string]: T; +} +let keys: keyof Map; // string +let value: Map['foo']; // number +``` + +# Mapped types + +A common task is to take an existing type and make each of its properties optional: + +```ts +interface PersonPartial { + name?: string; + age?: number; +} +``` + +Or we might want a readonly version: + +```ts +interface PersonReadonly { + readonly name: string; + readonly age: number; +} +``` + +This happens often enough in Javascript that TypeScript provides a way to create new types based on old types — **mapped types**. +In a mapped type, the new type transforms each property in the old type in the same way. +For example, you can make all properties of a type `readonly` or optional. +Here are a couple of examples: + +```ts +type Readonly = { + readonly [P in keyof T]: T[P]; +} +type Partial = { + [P in keyof T]?: T[P]; +} +``` + +And to use it: + +```ts +type PersonPartial = Partial; +type ReadonlyPerson = Readonly; +``` + +Let's take a look at the simplest mapped type and its parts: + +```ts +type Keys = 'option1' | 'option2'; +type Flags = { [K in Keys]: boolean }; +``` + +The syntax resembles the syntax for index signatures with a `for .. in` inside. +There are three parts: + +1. The type variable `K`, which gets bound to each property in turn. +2. The string literal union `Keys`, which contains the names of properties to iterate over. +3. The resulting type of the property. + +In this simple example, `Keys` is a hard-coded list of property names and the property type is always `boolean`, so this mapped type is equivalent to writing: + +```ts +type Flags = { + option1: boolean; + option2: boolean; +} +``` + +Real applications, however, look like `Readonly` or `Partial` above. +They're based on some existing type, and they transform the fields in some way. +That's where `keyof` and indexed access types come in: + +```ts +type NullablePerson = { [P in keyof Person]: Person[P] | null } +type PartialPerson = { [P in keyof Person]?: Person[P] } +``` + +But it's more useful to have a general version. + +```ts +type Nullable = { [P in keyof T]: T[P] | null } +type Partial = { [P in keyof T]?: T[P] } +``` + +In these examples, the properties list is `keyof T` and the resulting type is some variant of `T[P]`. +This is a good template for any general use of mapped types. +Here's one more example, in which `T[P]` is wrapped in a `Proxy` class: + +```ts +type Proxy = { + get(): T; + set(value: T): void; +} +type Proxify = { + [P in keyof T]: Proxy; +} +function proxify(o: T): Proxify { + // ... wrap proxies ... +} +let proxyProps = proxify(props); +``` + +Note that `Readonly` and `Partial` are so useful, they are included in TypeScript's standard libarary along with `Pick` and `Record`: + +```ts +type Pick = { + [P in K]: T[P]; +} +type Record = { + [P in K]: T; +} +``` + +## Inference from mapped types + +Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them. +Fortunately, that's pretty easy: + +```ts +function unproxify(t: Proxify): T { + let result = {} as T; + for (const k in t) { + result[k] = t[k].get(); + } + return result; +} + +let originalProps = unproxify(proxyProps); +``` + +Note that this unwrapping inference works best on *homomorphic* mapped types. +Homomorphic mapped types are mapped types that iterate over every property of some type, and only those properties: `{ [P in keyof T]: X }`. +In the examples above, `Nullable` and `Partial` are homomorphic whereas `Pick` and `Record` are not. +One clue is that `Pick` and `Record` both take a union of property names in addition to a source type, which they use instead of `keyof T`. +If the mapped type is not homomorphic you might have to explicitly give a type parameter to your unwrapping function.