mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Mapped Object Types
This commit is contained in:
parent
eaca2a7a74
commit
38798b2c01
159
doc/spec/Mapped Object Types.md
Normal file
159
doc/spec/Mapped Object Types.md
Normal file
@ -0,0 +1,159 @@
|
||||
|
||||
# Mapped Object Types { #mapped-object-types }
|
||||
|
||||
   `{` `readonly`<sub>opt</sub> `[`*Identifier* `in` *Type*`]` `?`<sub>opt</sub> `:` *Type* `}`
|
||||
|
||||
A ***mapped object type*** is a type operator that operates on types assignable to the string type, but primarily on unions of string literal types.
|
||||
|
||||
In the above syntax,
|
||||
|
||||
* The first *Type* (immediately following the `in` keyword) is the operand *K* of a mapped type.
|
||||
* The second *Type* forms the *property type template* of a mapped type, *T*.
|
||||
* The *Identifier* forms a type variable *P* that is scoped only in *T*, and is bound and constrained to *K*.
|
||||
|
||||
Mapped object types are primarily meant to iterate over a union of string literal types, and to generate a new object type containing properties whose names are based on each string literal within that union.
|
||||
For example, in the below example, the type aliases `Foo` and `Bar` are equivalent even though `Foo` aliases an object type literal, and `Bar` aliases a mapped object type.
|
||||
|
||||
```ts
|
||||
type Foo = { hello: string, beautiful: string, world: string; };
|
||||
|
||||
type Bar = { [P in "hello" | "beautiful" | "world"]: string };
|
||||
```
|
||||
|
||||
The operand is not required to be a union of string literal types, but is only required to be a assignable to string.
|
||||
|
||||
```ts
|
||||
type A1 = { [P in "hello"]: string };
|
||||
type A2 = { hello: string };
|
||||
|
||||
type B1 = { [P in string]: number };
|
||||
type B2 = { [P in any]: number };
|
||||
type B3 = { [propName: string]: number };
|
||||
```
|
||||
|
||||
In the above, `A1` is equivalent to `A2`, and both types contain only a single property named `hello` of type `string.
|
||||
`B1`, `B2`, and `B3` are also equivalent, and introduce object types with only a string index signature.
|
||||
|
||||
Like with property names, mapped object type allow the `readonly` and the `?` optionality modifiers to be specified as well.
|
||||
Specifying a `readonly` modifier on a mapped type results in each property or index signature of the mapped object type becoming read-only.
|
||||
Similarly, specifying a `?` results in each property becoming optional, or in the case where an index signature is generated, an index signature whose type forms a union with the Undefined type.
|
||||
In the below example, `A1` is equivalent to `A2`, `B1` is equivalent to `B2`, and `C1` is equivalent to `C2`.
|
||||
|
||||
```ts
|
||||
type A1 = { readonly [P in "hello" | "world" ]: string };
|
||||
type A2 = {
|
||||
readonly hello: string,
|
||||
readonly world: string,
|
||||
}
|
||||
|
||||
type B1 = { [P in "foo" | "bar"]?: number };
|
||||
type B2 = {
|
||||
foo?: number,
|
||||
bar?: number,
|
||||
};
|
||||
|
||||
type C1 = { readonly [P in string]?: boolean };
|
||||
type C2 = {
|
||||
readonly [propName: string]: boolean | undefined;
|
||||
};
|
||||
```
|
||||
|
||||
As mentioned, mapped object types introduce a type variable *P*.
|
||||
When *K* is not a generic type, as seen thus far, then when generating each property of a mapped object type, the type of that property is *T* with instances of *P* substituted with a string literal type whose contents are equivalent to the property name itself.
|
||||
Similarly, when generating an index signature, the type of that index signature prior to accounting for optionality is *T* with instances of *P* substituted with *K*.
|
||||
In the following example, each pair `A1` and `A2`, `B1` and `B2`, `C1` and `C2`, `D1` and `D2`, are respectively equivalent.
|
||||
|
||||
```ts
|
||||
type A1 = { [P in "hello" | "world"]: P };
|
||||
type A2 = {
|
||||
hello: "hello",
|
||||
world: "world",
|
||||
};
|
||||
|
||||
type B1 = { [P in "hello" | "world"]: P | boolean };
|
||||
type B2 = {
|
||||
hello: "hello" | boolean,
|
||||
world: "world" | boolean,
|
||||
};
|
||||
|
||||
type C1 = { [P in string]: P };
|
||||
type C2 = {
|
||||
[propName: string]: string,
|
||||
};
|
||||
|
||||
type D1 = { [P in any]: P };
|
||||
type D2 = {
|
||||
[propName: string]: any,
|
||||
};
|
||||
```
|
||||
|
||||
This can be powerfully combined with key query types, and indexed access types:
|
||||
|
||||
```ts
|
||||
interface TypeMap {
|
||||
"str": string,
|
||||
"num": number,
|
||||
"bool": boolean,
|
||||
}
|
||||
|
||||
interface SchemaType {
|
||||
foo: "str",
|
||||
bar: "num",
|
||||
baz: "bool",
|
||||
}
|
||||
|
||||
type TypeScriptType = {
|
||||
[P in keyof SchemaType]: TypeMap[P]
|
||||
};
|
||||
|
||||
// Equivalent to...
|
||||
interface TypeScriptType {
|
||||
foo: string,
|
||||
bar: number,
|
||||
baz: boolean,
|
||||
}
|
||||
```
|
||||
|
||||
## Homomorphic Mapped Object Types { #homomorphic-mapped-object types }
|
||||
|
||||
A ***homomorphic mapped object type*** is a mapped type of a particular form, where the operand *K* is a type query `keyof` *O*.
|
||||
|
||||
In such instances, TypeScript will consult the type `O` when generating each property and index signature for respective modifiers.
|
||||
If a modifier is not specified in the mapped type itself, but is specified for a given property in *O*, then the resulting property inherits that same modifier.
|
||||
For example, in the following, `A` and `B` are equivalent types, but `C` is not because it does not represent a homomorphic mapped type.
|
||||
As a result, the `baz` property is `readonly` in `A` and `B`, but not in `C`.
|
||||
|
||||
```ts
|
||||
interface T {
|
||||
foo?: number
|
||||
bar: number;
|
||||
readonly baz?: string;
|
||||
}
|
||||
|
||||
type A = {
|
||||
foo?: number;
|
||||
bar: number;
|
||||
readonly baz?: string;
|
||||
}
|
||||
|
||||
type B = {
|
||||
[P in keyof T]?: T[P];
|
||||
}
|
||||
|
||||
type C = {
|
||||
[P in "foo" | "bar" | "baz"]?: T[P];
|
||||
}
|
||||
```
|
||||
|
||||
## Generic mapped types
|
||||
|
||||
A ***generic mapped type** is one whose constraint is a type parameter, a generic indexed access type, or a generic key query type, or a union containing any of the aforementiond types.
|
||||
|
||||
Mapped object type syntax results in an object type with a series of different members depending on the operand type.
|
||||
|
||||
* If *K* is the String type, then an object type is produced where *T* will be instantiated as *T'* in which instances of *P* in *T* have been substituted with *string*, and the produced object type contains a string index signature of type *T'* with the specified `readonly` and optionality modifiers.
|
||||
* If *K* is declared as a key query operator `keyof` *O*, or as a type parameter whose constraint is a key query operator `keyof` *O*, then where the apparent type *O'* of *O*,
|
||||
* if *O'* is the Any type, then an object type is produced where *T* will be instantiated as *T'* in which instances of *P* in *T* have been substituted with *string*, and the produced object type contains a string index signature of type *T'* with the specified `readonly` modifier.
|
||||
* otherwise, an object type is produced with each property *P'* declared in *O'* with the type of *T'* in which instances of *P* in *T'* have been substituted with the string where *P* has *O'*`[`*P'*`]` with the same modifiers of *P'* in *O'*, as well as the specified `readonly` and optionality modifiers.
|
||||
* If *K* is a string literal type, or a union of strictly string literal types, then an object type is produced where for each string literal constituent *S* of *K*, *T* will be instantiated as *T'* in which instances of *P* have been substituted with *S*, and the produced object type contains a property whose name is identical to the contents of *S*, and whose respective type is *T'*.
|
||||
*TODO "named types" is the only place that talks about instantiation*
|
||||
Loading…
x
Reference in New Issue
Block a user