Variable Declarations
When you are declaring a new variable, you may optionally declare its type.
JavaScript has three ways of declaring local variables:
var
- declares a variable, optionally assigning a value. (MDN)let
- declares a block-scoped variable, optionally assigning a value. (MDN)const
- declares a block-scoped variable, assigning a value that cannot be re-assigned. (MDN)
In Flow these fall into two groups:
let
andvar
- variables that can be reassigned.const
- variables that cannot be reassigned.
1var varVariable = 1;2let letVariable = 1;3const constVariable = 1;4
5varVariable = 2; // Works!6letVariable = 2; // Works!7constVariable = 2; // Error!
7:1-7:13: Cannot reassign constant `constVariable` [1]. [reassign-const]
const
Since a const
variable cannot be re-assigned at a later time it is fairly
simple.
Flow can either infer the type from the value you are assigning to it or you can provide it with a type.
1const foo /* : number */ = 1;2const bar: number = 2;
var
and let
≥0.186
Since var
and let
can be re-assigned, there's a few more rules you'll need
to know about.
When you provide a type, you will be able to re-assign the value, but it must always be of a compatible type.
1let foo: number = 1;2foo = 2; // Works!3foo = "3"; // Error!
3:7-3:9: Cannot assign `"3"` to `foo` because string [1] is incompatible with number [2]. [incompatible-type]
When the variable has no annotation, Flow infers a precise type based on their initializer or initial assignment. All subsequent assignments to that variable will be constrained by this type. This section shows some examples of how Flow determines what type an unannotated variable is inferred to have.
If you want a variable to have a different type than what Flow infers for it, you can always add a type annotation to the variable’s declaration. That will override everything discussed in this page!
Variables initialized at their declarations
The common case for unannotated variables is very straightforward: when a
variable is declared with an initializer that is not the literal null
, that
variable will from then on have the type of the initializer, and future writes
to the variable will be constrained by that type.
1import * as React from 'react';2
3type Props = $ReadOnly<{ prop: string }>;4
5declare var x: number;6declare var y: number;7declare var props: Props;8
9let product = Math.sqrt(x) + y;10// `product` has type `number`11
12let Component = ({prop}: Props): React.Node => { return <div/> }13// `Component` has type`React.ComponentType<Props>`14
15let element = <Component {...props} />16// `element` has type `React.Element<React.ComponentType<Props>>`17
18/* Let's define a new component */19
20type OtherProps = $ReadOnly<{ ...Props, extra_prop: number }>;21declare var OtherComponent: (OtherProps) => React.Node;22declare var other_props: OtherProps23
24/* Any subsequent assignments to `product`, `Component`, or `element` will be25 * checked against the types that Flow infers for the initializers, and if26 * conflicting types are assigned, Flow will signal an error. */27
28product = "Our new product is..."; 29Component = ({prop}: OtherProps): React.Node => { return <div/> }; 30element = <OtherComponent {...other_props} />;
28:11-28:33: Cannot assign `'Our new pr...'` to `product` because string [1] is incompatible with number [2]. All writes to `product` must be compatible with the type of its initializer [3]. Add an annotation to `product` [3] if a different type is desired. [incompatible-type]29:13-29:65: Cannot assign function to `Component` because property `extra_prop` is missing in object type [1] but exists in object type [2] in the first parameter. All writes to `Component` must be compatible with the type of its initializer [3]. Add an annotation to `Component` [3] if a different type is desired. [prop-missing]30:11-30:45: Cannot assign `<OtherComponent />` to `element` because property `extra_prop` is missing in object type [1] but exists in object type [2] in type argument `P` [3]. All writes to `element` must be compatible with the type of its initializer [4]. Add an annotation to `element` [4] if a different type is desired. [prop-missing]30:12-30:25: Cannot assign `<OtherComponent />` to `element` because property `extra_prop` is missing in object type [1] but exists in object type [2] in the first parameter of type argument `ElementType` [3]. All writes to `element` must be compatible with the type of its initializer [4]. Add an annotation to `element` [4] if a different type is desired. [prop-missing]
If you want these examples to typecheck, and for Flow to realize that different kinds of values can be written to these variables, you must add a type annotation reflecting this more general type to their declarations:
let product: number | string = ...
let Component: mixed = ... // No good type to represent this! Consider restructuring
let element: React.Node = ...
Variables declared without initializers
Often variables are declared without initializers. In such cases, Flow will try to choose the "first" assignment or assignments to the variable to define its type. "First" here means both top-to-bottom and nearer-scope to deeper-scope—we’ll try to choose an assignment that happens in the same function scope as the variable’s declaration, and only look inside nested functions if we don’t find any assignments locally:
1let topLevelAssigned;2function helper() {3 topLevelAssigned = 42; // Error: `topLevelAssigned` has type `string` 4}5topLevelAssigned = "Hello world"; // This write determines the var's type6topLevelAssigned = true; // Error: `topLevelAssigned` has type `string`
3:22-3:23: Cannot assign `42` to `topLevelAssigned` because number [1] is incompatible with string [2]. All writes to `topLevelAssigned` must be compatible with the type of its initial assignment [3]. Add an annotation to `topLevelAssigned` [4] if a different type is desired. [incompatible-type]6:20-6:23: Cannot assign `true` to `topLevelAssigned` because boolean [1] is incompatible with string [2]. All writes to `topLevelAssigned` must be compatible with the type of its initial assignment [3]. Add an annotation to `topLevelAssigned` [4] if a different type is desired. [incompatible-type]
If there are two or more possible "first assignments," due to an if
- or
switch
-statement, they’ll both count—this is one of the few ways that Flow
will still infer unions for variable types:
1let myNumberOrString;2declare var condition: boolean;3if (condition) {4 myNumberOrString = 42; // Determines type5} else {6 myNumberOrString = "Hello world"; // Determines type7}8myNumberOrString = 21; // fine, compatible with type9myNumberOrString = "Goodbye"; // fine, compatible with type10myNumberOrString = false; // Error: `myNumberOrString` has type `number | string`
10:20-10:24: Cannot assign `false` to `myNumberOrString` because: [incompatible-type] Either boolean [1] is incompatible with number [2]. Or boolean [1] is incompatible with string [3]. All writes to `myNumberOrString` must be compatible with the type of one of its initial assignments [4], [5]. Add an annotation to `myNumberOrString` [6] if a different type is desired.
This only applies when the variable is written to in both branches, however. If only one branch contains a write, that write becomes the type of the variable afterwards (though Flow will still check to make sure that the variable is definitely initialized):
1let oneBranchAssigned;2declare var condition: boolean;3if (condition) {4 oneBranchAssigned = "Hello world!";5}6oneBranchAssigned.toUpperCase(); // Error: `oneBranchAssigned` may be uninitialized 7oneBranchAssigned = 42; // Error: `oneBranchAssigned` has type `string`
6:19-6:29: Cannot call `oneBranchAssigned.toUpperCase` because property `toUpperCase` is missing in possibly uninitialized variable [1]. [incompatible-use]7:21-7:22: Cannot assign `42` to `oneBranchAssigned` because number [1] is incompatible with string [2]. All writes to `oneBranchAssigned` must be compatible with the type of its initial assignment [3]. Add an annotation to `oneBranchAssigned` [4] if a different type is desired. [incompatible-type]
Variables initialized to null
Finally, the one exception to the general principle that variable’s types are
determined by their first assignment(s) is when a variable is initialized as (or
whose first assignment is) the literal value null
. In such cases, the next
non-null assignment (using the same rules as above) determines the rest of the
variable’s type, and the overall type of the variable becomes a union of null
and the type of the subsequent assignment. This supports the common pattern
where a variable starts off as null
before getting assigned by a value of some
other type:
1function findIDValue<T>(dict: {[key: string]: T}): T {2 let idVal = null; // initialized as `null`3 for (const key in dict) {4 if (key === 'ID') {5 idVal = dict[key]; // Infer that `idVal` has type `null | T`6 }7 }8 if (idVal === null) {9 throw new Error("No entry for ID!");10 }11 return idVal;12}
Catch variables ≥0.197
If a catch
variable does not have an annotation, its default type is any
.
You can optionally annotate it with exactly mixed
or any
. E.g.
1try {2} catch (e: mixed) {3 if (e instanceof TypeError) {4 e as TypeError; // OK5 } else if (e instanceof Error) {6 e as Error; // OK7 } else {8 throw e;9 }10}
By using mixed
, you can improve your safety and Flow coverage,
at the trade-off of increased runtime checks.
You can change the default type of catch
variables when there is no annotation by setting the use_mixed_in_catch_variables
option to true.