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
and var
- variables that can be reassigned.
const
- variables that cannot be reassigned.
1
2
3
4
5
6
7
8
|
var varVariable = 1;
let letVariable = 1;
const constVariable = 1;
varVariable = 2;
letVariable = 2;
constVariable = 2;
|
{"value":"var varVariable = 1;\nlet letVariable = 1;\nconst constVariable = 1;\n\nvarVariable = 2; // Works!\nletVariable = 2; // Works!\n// $ExpectError\nconstVariable = 2; // Error!\n","tokens":[{"type":"T_VAR","context":"normal","value":"var","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"varVariable","line":1,"start":4,"end":15},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":16,"end":17},{"type":"T_NUMBER","context":"normal","value":"1","line":1,"start":18,"end":19},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":19,"end":20},{"type":"T_LET","context":"normal","value":"let","line":2,"start":21,"end":24},{"type":"T_IDENTIFIER","context":"normal","value":"letVariable","line":2,"start":25,"end":36},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":37,"end":38},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":39,"end":40},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":40,"end":41},{"type":"T_CONST","context":"normal","value":"const","line":3,"start":42,"end":47},{"type":"T_IDENTIFIER","context":"normal","value":"constVariable","line":3,"start":48,"end":61},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":62,"end":63},{"type":"T_NUMBER","context":"normal","value":"1","line":3,"start":64,"end":65},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":65,"end":66},{"type":"T_IDENTIFIER","context":"normal","value":"varVariable","line":5,"start":68,"end":79},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":80,"end":81},{"type":"T_NUMBER","context":"normal","value":"2","line":5,"start":82,"end":83},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":83,"end":84},{"type":"Line","context":"comment","value":"// Works!","line":5,"start":87,"end":96},{"type":"T_IDENTIFIER","context":"normal","value":"letVariable","line":6,"start":97,"end":108},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":109,"end":110},{"type":"T_NUMBER","context":"normal","value":"2","line":6,"start":111,"end":112},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":112,"end":113},{"type":"Line","context":"comment","value":"// Works!","line":6,"start":116,"end":125},{"type":"Line","context":"comment","value":"// $ExpectError","line":7,"start":126,"end":141},{"type":"T_IDENTIFIER","context":"normal","value":"constVariable","line":8,"start":142,"end":155},{"type":"T_ASSIGN","context":"normal","value":"=","line":8,"start":156,"end":157},{"type":"T_NUMBER","context":"normal","value":"2","line":8,"start":158,"end":159},{"type":"T_SEMICOLON","context":"normal","value":";","line":8,"start":159,"end":160},{"type":"Line","context":"comment","value":"// Error!","line":8,"start":161,"end":170}],"errors":[]}
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.
1
2
3
|
const foo /* : number */ = 1;
const bar: number = 2;
|
{"value":"// @flow\nconst foo /* : number */ = 1;\nconst bar: number = 2;\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_CONST","context":"normal","value":"const","line":2,"start":9,"end":14},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":2,"start":15,"end":18},{"type":"T_COLON","context":"type","value":":","line":2,"start":19,"end":23},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":2,"start":24,"end":30},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":34,"end":35},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":36,"end":37},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":37,"end":38},{"type":"T_CONST","context":"normal","value":"const","line":3,"start":39,"end":44},{"type":"T_IDENTIFIER","context":"normal","value":"bar","line":3,"start":45,"end":48},{"type":"T_COLON","context":"type","value":":","line":3,"start":48,"end":49},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":50,"end":56},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":57,"end":58},{"type":"T_NUMBER","context":"normal","value":"2","line":3,"start":59,"end":60},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":60,"end":61}],"errors":[]}
var
and let
Since var
and let
can be re-assigned, there’s a few more rules you’ll need
to know about.
Similar to const
, Flow can either infer the type from the value you are
assigning to it or you can provide it with a type:
1
2
3
4
5
|
var fooVar /* : number */ = 1;
let fooLet /* : number */ = 1;
var barVar: number = 2;
let barLet: number = 2;
|
{"value":"// @flow\nvar fooVar /* : number */ = 1;\nlet fooLet /* : number */ = 1;\nvar barVar: number = 2;\nlet barLet: number = 2;\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_VAR","context":"normal","value":"var","line":2,"start":9,"end":12},{"type":"T_IDENTIFIER","context":"normal","value":"fooVar","line":2,"start":13,"end":19},{"type":"T_COLON","context":"type","value":":","line":2,"start":20,"end":24},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":2,"start":25,"end":31},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":35,"end":36},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":37,"end":38},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":38,"end":39},{"type":"T_LET","context":"normal","value":"let","line":3,"start":40,"end":43},{"type":"T_IDENTIFIER","context":"normal","value":"fooLet","line":3,"start":44,"end":50},{"type":"T_COLON","context":"type","value":":","line":3,"start":51,"end":55},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":56,"end":62},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":66,"end":67},{"type":"T_NUMBER","context":"normal","value":"1","line":3,"start":68,"end":69},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":69,"end":70},{"type":"T_VAR","context":"normal","value":"var","line":4,"start":71,"end":74},{"type":"T_IDENTIFIER","context":"normal","value":"barVar","line":4,"start":75,"end":81},{"type":"T_COLON","context":"type","value":":","line":4,"start":81,"end":82},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":4,"start":83,"end":89},{"type":"T_ASSIGN","context":"normal","value":"=","line":4,"start":90,"end":91},{"type":"T_NUMBER","context":"normal","value":"2","line":4,"start":92,"end":93},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":93,"end":94},{"type":"T_LET","context":"normal","value":"let","line":5,"start":95,"end":98},{"type":"T_IDENTIFIER","context":"normal","value":"barLet","line":5,"start":99,"end":105},{"type":"T_COLON","context":"type","value":":","line":5,"start":105,"end":106},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":5,"start":107,"end":113},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":114,"end":115},{"type":"T_NUMBER","context":"normal","value":"2","line":5,"start":116,"end":117},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":117,"end":118}],"errors":[]}
When you provide a type, you will be able to re-assign the value, but it must
always be of a compatible type.
1
2
3
4
5
|
let foo: number = 1;
foo = 2;
foo = "3";
|
Cannot assign `"3"` to `foo` because string [1] is incompatible with number [2]. [incompatible-type]
{"value":"// @flow\nlet foo: number = 1;\nfoo = 2; // Works!\n// $ExpectError\nfoo = \"3\"; // Error!\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_LET","context":"normal","value":"let","line":2,"start":9,"end":12},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":2,"start":13,"end":16},{"type":"T_COLON","context":"type","value":":","line":2,"start":16,"end":17},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":2,"start":18,"end":24},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":25,"end":26},{"type":"T_NUMBER","context":"normal","value":"1","line":2,"start":27,"end":28},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":28,"end":29},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":3,"start":30,"end":33},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":34,"end":35},{"type":"T_NUMBER","context":"normal","value":"2","line":3,"start":36,"end":37},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":37,"end":38},{"type":"Line","context":"comment","value":"// Works!","line":3,"start":41,"end":50},{"type":"Line","context":"comment","value":"// $ExpectError","line":4,"start":51,"end":66},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":5,"start":67,"end":70},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":71,"end":72},{"type":"T_STRING","context":"normal","value":"\"3\"","line":5,"start":73,"end":76},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":76,"end":77},{"type":"Line","context":"comment","value":"// Error!","line":5,"start":78,"end":87}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot assign `\"3\"` to `foo` because string [1] is incompatible with number [2]. [incompatible-type]","context":"foo = \"3\"; // Error!","source":"-","start":{"line":5,"column":7,"offset":73},"end":{"line":5,"column":9,"offset":76}}],"operation":null}]}
When you do not provide a type, the inferred type will do one of two things if
you re-assign it.
Reassigning variables
By default when you re-assign a variable, Flow will give it the type of all
possible assignments.
1
2
3
4
5
6
|
let foo = 42;
if (Math.random()) foo = true;
if (Math.random()) foo = "hello";
let isOneOf: number | boolean | string = foo;
|
{"value":"let foo = 42;\n\nif (Math.random()) foo = true;\nif (Math.random()) foo = \"hello\";\n\nlet isOneOf: number | boolean | string = foo; // Works!\n","tokens":[{"type":"T_LET","context":"normal","value":"let","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":1,"start":4,"end":7},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":8,"end":9},{"type":"T_NUMBER","context":"normal","value":"42","line":1,"start":10,"end":12},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":12,"end":13},{"type":"T_IF","context":"normal","value":"if","line":3,"start":15,"end":17},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":18,"end":19},{"type":"T_IDENTIFIER","context":"normal","value":"Math","line":3,"start":19,"end":23},{"type":"T_PERIOD","context":"normal","value":".","line":3,"start":23,"end":24},{"type":"T_IDENTIFIER","context":"normal","value":"random","line":3,"start":24,"end":30},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":30,"end":31},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":31,"end":32},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":3,"start":34,"end":37},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":38,"end":39},{"type":"T_TRUE","context":"normal","value":"true","line":3,"start":40,"end":44},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":44,"end":45},{"type":"T_IF","context":"normal","value":"if","line":4,"start":46,"end":48},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"normal","value":"Math","line":4,"start":50,"end":54},{"type":"T_PERIOD","context":"normal","value":".","line":4,"start":54,"end":55},{"type":"T_IDENTIFIER","context":"normal","value":"random","line":4,"start":55,"end":61},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":61,"end":62},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":62,"end":63},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":63,"end":64},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":4,"start":65,"end":68},{"type":"T_ASSIGN","context":"normal","value":"=","line":4,"start":69,"end":70},{"type":"T_STRING","context":"normal","value":"\"hello\"","line":4,"start":71,"end":78},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":78,"end":79},{"type":"T_LET","context":"normal","value":"let","line":6,"start":81,"end":84},{"type":"T_IDENTIFIER","context":"normal","value":"isOneOf","line":6,"start":85,"end":92},{"type":"T_COLON","context":"type","value":":","line":6,"start":92,"end":93},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":6,"start":94,"end":100},{"type":"T_BIT_OR","context":"type","value":"|","line":6,"start":101,"end":102},{"type":"T_BOOLEAN_TYPE","context":"type","value":"boolean","line":6,"start":103,"end":110},{"type":"T_BIT_OR","context":"type","value":"|","line":6,"start":111,"end":112},{"type":"T_STRING_TYPE","context":"type","value":"string","line":6,"start":113,"end":119},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":120,"end":121},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":6,"start":122,"end":125},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":125,"end":126},{"type":"Line","context":"comment","value":"// Works!","line":6,"start":127,"end":136}],"errors":[]}
Sometimes Flow is able to figure out (with certainty) the type of a variable
after re-assignment. In that case, Flow will give it the known type.
1
2
3
4
5
6
7
8
9
|
let foo = 42;
let isNumber: number = foo;
foo = true;
let isBoolean: boolean = foo;
foo = "hello";
let isString: string = foo;
|
{"value":"// @flow\nlet foo = 42;\nlet isNumber: number = foo; // Works!\n\nfoo = true;\nlet isBoolean: boolean = foo; // Works!\n\nfoo = \"hello\";\nlet isString: string = foo; // Works!\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_LET","context":"normal","value":"let","line":2,"start":9,"end":12},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":2,"start":13,"end":16},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":17,"end":18},{"type":"T_NUMBER","context":"normal","value":"42","line":2,"start":19,"end":21},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":21,"end":22},{"type":"T_LET","context":"normal","value":"let","line":3,"start":23,"end":26},{"type":"T_IDENTIFIER","context":"normal","value":"isNumber","line":3,"start":27,"end":35},{"type":"T_COLON","context":"type","value":":","line":3,"start":35,"end":36},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":37,"end":43},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":44,"end":45},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":3,"start":46,"end":49},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":49,"end":50},{"type":"Line","context":"comment","value":"// Works!","line":3,"start":51,"end":60},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":5,"start":62,"end":65},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":66,"end":67},{"type":"T_TRUE","context":"normal","value":"true","line":5,"start":68,"end":72},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":72,"end":73},{"type":"T_LET","context":"normal","value":"let","line":6,"start":74,"end":77},{"type":"T_IDENTIFIER","context":"normal","value":"isBoolean","line":6,"start":78,"end":87},{"type":"T_COLON","context":"type","value":":","line":6,"start":87,"end":88},{"type":"T_BOOLEAN_TYPE","context":"type","value":"boolean","line":6,"start":89,"end":96},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":97,"end":98},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":6,"start":99,"end":102},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":102,"end":103},{"type":"Line","context":"comment","value":"// Works!","line":6,"start":104,"end":113},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":8,"start":115,"end":118},{"type":"T_ASSIGN","context":"normal","value":"=","line":8,"start":119,"end":120},{"type":"T_STRING","context":"normal","value":"\"hello\"","line":8,"start":121,"end":128},{"type":"T_SEMICOLON","context":"normal","value":";","line":8,"start":128,"end":129},{"type":"T_LET","context":"normal","value":"let","line":9,"start":130,"end":133},{"type":"T_IDENTIFIER","context":"normal","value":"isString","line":9,"start":134,"end":142},{"type":"T_COLON","context":"type","value":":","line":9,"start":142,"end":143},{"type":"T_STRING_TYPE","context":"type","value":"string","line":9,"start":144,"end":150},{"type":"T_ASSIGN","context":"normal","value":"=","line":9,"start":151,"end":152},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":9,"start":153,"end":156},{"type":"T_SEMICOLON","context":"normal","value":";","line":9,"start":156,"end":157},{"type":"Line","context":"comment","value":"// Works!","line":9,"start":158,"end":167}],"errors":[]}
If statements, functions, and other conditionally run code can all prevent Flow
from being able to figure out precisely what a type will be.
1
2
3
4
5
6
7
8
9
10
11
12
|
let foo = 42;
function mutate() {
foo = true;
foo = "hello";
}
mutate();
let isString: string = foo;
|
Cannot assign `foo` to `isString` because number [1] is incompatible with string [2]. [incompatible-type]
Cannot assign `foo` to `isString` because boolean [1] is incompatible with string [2]. [incompatible-type]
{"value":"// @flow\nlet foo = 42;\n\nfunction mutate() {\n foo = true;\n foo = \"hello\";\n}\n\nmutate();\n\n// $ExpectError\nlet isString: string = foo; // Error!\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_LET","context":"normal","value":"let","line":2,"start":9,"end":12},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":2,"start":13,"end":16},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":17,"end":18},{"type":"T_NUMBER","context":"normal","value":"42","line":2,"start":19,"end":21},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":21,"end":22},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":24,"end":32},{"type":"T_IDENTIFIER","context":"normal","value":"mutate","line":4,"start":33,"end":39},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":39,"end":40},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":40,"end":41},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":42,"end":43},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":5,"start":46,"end":49},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":50,"end":51},{"type":"T_TRUE","context":"normal","value":"true","line":5,"start":52,"end":56},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":6,"start":60,"end":63},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":64,"end":65},{"type":"T_STRING","context":"normal","value":"\"hello\"","line":6,"start":66,"end":73},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":73,"end":74},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":75,"end":76},{"type":"T_IDENTIFIER","context":"normal","value":"mutate","line":9,"start":78,"end":84},{"type":"T_LPAREN","context":"normal","value":"(","line":9,"start":84,"end":85},{"type":"T_RPAREN","context":"normal","value":")","line":9,"start":85,"end":86},{"type":"T_SEMICOLON","context":"normal","value":";","line":9,"start":86,"end":87},{"type":"Line","context":"comment","value":"// $ExpectError","line":11,"start":89,"end":104},{"type":"T_LET","context":"normal","value":"let","line":12,"start":105,"end":108},{"type":"T_IDENTIFIER","context":"normal","value":"isString","line":12,"start":109,"end":117},{"type":"T_COLON","context":"type","value":":","line":12,"start":117,"end":118},{"type":"T_STRING_TYPE","context":"type","value":"string","line":12,"start":119,"end":125},{"type":"T_ASSIGN","context":"normal","value":"=","line":12,"start":126,"end":127},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":12,"start":128,"end":131},{"type":"T_SEMICOLON","context":"normal","value":";","line":12,"start":131,"end":132},{"type":"Line","context":"comment","value":"// Error!","line":12,"start":133,"end":142}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot assign `foo` to `isString` because number [1] is incompatible with string [2]. [incompatible-type]","context":"let isString: string = foo; // Error!","source":"-","start":{"line":12,"column":24,"offset":128},"end":{"line":12,"column":26,"offset":131}}],"operation":null},{"id":"E2","messages":[{"id":"E2M1","description":"Cannot assign `foo` to `isString` because boolean [1] is incompatible with string [2]. [incompatible-type]","context":"let isString: string = foo; // Error!","source":"-","start":{"line":12,"column":24,"offset":128},"end":{"line":12,"column":26,"offset":131}}],"operation":null}]}
As Flow gets smarter and smarter, there should be fewer instances of these scenarios.