Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit 22892f1

Browse files
committed
Discuss mapped type inference and call out terms better
1 parent bc3f9e3 commit 22892f1

File tree

1 file changed

+27
-4
lines changed

1 file changed

+27
-4
lines changed

pages/Advanced Types.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ function pluck(o, names) {
294294
}
295295
```
296296

297-
Here's how you would write and use this function in TypeScript:
297+
Here's how you would write and use this function in TypeScript, using the **index type query** and **indexed access** operators:
298298

299299
```ts
300300
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
@@ -311,7 +311,7 @@ let strings: string[] = pluck(person, ['name']); // ok, string[]
311311

312312
The compiler checks that `name` is actually a property on `Person`, and it knows that `strings` is a `string[]` because `name` is a `string`.
313313
To make this work, the example introduces a couple of new type operators.
314-
First is `keyof T`, the index type query operator.
314+
First is `keyof T`, the **index type query operator**.
315315
For any type `T`, `keyof T` is the union of known, public property names of `T`.
316316
For example:
317317

@@ -328,7 +328,7 @@ That means the compiler will check that you pass the right set of property names
328328
pluck(person, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age'
329329
```
330330

331-
The second operator is `T[K]`, the indexed access operator.
331+
The second operator is `T[K]`, the **indexed access operator**.
332332
Here, the type syntax reflects the expression syntax.
333333
That means that `person['name']` has the type `Person['name']` &mdash; which in our example is just `string`.
334334
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 {
384384
}
385385
```
386386

387-
This happens often enough in Javascript that TypeScript provides a way to create new types based on old types &mdash; mapped types.
387+
This happens often enough in Javascript that TypeScript provides a way to create new types based on old types &mdash; **mapped types**.
388388
In a mapped type, the new type transforms each property in the old type in the same way.
389389
For example, you can make all properties of a type `readonly` or optional.
390390
Here are a couple of examples:
@@ -473,6 +473,29 @@ type Record<K extends string | number, T> = {
473473
}
474474
```
475475
476+
## Inference from mapped types
477+
478+
Now that you know how to wrap the properties of a type, the next thing you'll want to do is unwrap them.
479+
Fortunately, that's pretty easy:
480+
481+
```ts
482+
function unproxify<T>(t: Proxify<T>): T {
483+
let result = {} as T;
484+
for (const k in t) {
485+
result[k] = t[k].get();
486+
}
487+
return result;
488+
}
489+
490+
let originalProps = unproxify(proxyProps);
491+
```
492+
493+
Note that this unwrapping inference works best on *homomorphic* mapped types.
494+
Homomorphic mapped types are mapped types that iterate over every property of some type, and only those properties: `{ [P in keyof T]: X }`.
495+
In the examples above, `Nullable` and `Partial` are homomorphic whereas `Pick` and `Record` are not.
496+
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`.
497+
If the mapped type is not homomorphic you might have to explicitly give a type parameter to your unwrapping function.
498+
476499
# Type Aliases
477500

478501
Type aliases create a new name for a type.

0 commit comments

Comments
 (0)