diff --git a/website/docs/types/const-expression.md b/website/docs/types/const-expression.md new file mode 100644 index 00000000000..986d1102c61 --- /dev/null +++ b/website/docs/types/const-expression.md @@ -0,0 +1,78 @@ +--- +title: Const Expressions +slug: /types/const-expression +--- + +Sometimes it is useful to specifiy that a literal expression is expected to be immutable. +In such cases, you can annotate the expression with the `as const` modifier. We +refer to these expressions as const-expressions. + +## Typing for Const Expressions {#toc-const-expression-typing} + +The inferred type of const-expressions is the [singleton type](../literals) for primitive values +and the read-only versions for container types. Array literals are inferred as tuple types. + +Here are some examples of primitive values: +```js +42 as const; // inferred type is 42 + +"hello" as const; // inferred type is "hello" +``` + +Containers become read-only and the modifier is applied deeply +```js +{ f: 42 } as const; // {+f: 42} + +[42, "hello"] as const; // $ReadOnly<[42, "hello"]> + +{ f: { g: 42 } } as const; // {+f: {+g: 42}} +``` + +Note that the effect of the modifier does not persist through variables. For example +in +```js +const nonConstObject = { g: 42 }; +const constObject = { f: nonConstObject } as const; +``` +the type of `nonConstObject` will be `{g: number}` and the type of `constObject` will +be `{+f: {g: number}}`. In other words, only the top-level property `prop` will +be read-only. + +Finally, it is an error to apply `as const` to non-literal expressions: +```js flow-check +const x = 1; +const y = x as const; +``` + +## Typical const-expression example {#toc-const-expression-example} + +A common pattern where const-expressions are useful is in enum-like +structures that are not expected to be mutated. For example +```js +export const STATUS = { + INIT: 'INIT', + LOADING: 'LOADING', + SUCCESS: 'SUCCESS', + ERROR: 'ERROR', +} as const; +``` +The type of `STATUS.INIT` is `"INIT"`, the type of `STATUS.LOADING` is `"LOADING"` and so on. + +With this definition it is also possible to effectively lift the values of the various fields +to type annotations. For example +```js +type State = + | { +kind: typeof STATUS.INIT; } + | { +kind: typeof STATUS.LOADING; progress: number; } + | { +kind: typeof STATUS.SUCCESS; result: string; } + | { +kind: typeof STATUS.ERROR; msg: string; }; +``` +Without the use of `as const` the type `typeof STATUS.INIT` would be `string`, which +would make it unsuitable as a distinguishing tag in a disjoint union. + +## Adoption of `as const` syntax +To use the `as const` syntax, you need to upgrade your infrastructure: +- Flow and Flow Parser: 0.256+ +- Prettier: 3.1+ +- Babel: use the [babel-plugin-syntax-hermes-parser](https://www.npmjs.com/package/babel-plugin-syntax-hermes-parser) plugin version 0.19+, see our [Babel guide](../../tools/babel) for more details. +- ESLint: use [hermes-eslint](https://www.npmjs.com/package/hermes-eslint) plugin version 0.19+, see our [ESLint guide](../../tools/eslint) for more details. diff --git a/website/sidebars.js b/website/sidebars.js index 1302cb57509..9cd7e215c8f 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -54,6 +54,7 @@ module.exports = { 'types/type-guards', 'types/typeof', 'types/casting', + 'types/const-expression', 'types/utilities', 'types/modules', 'types/comments',