跳至主要内容

变量声明

声明新变量时,可以选择声明其类型。

JavaScript 有三种声明局部变量的方式

  • var - 声明一个变量,可以选择赋值。(MDN)
  • let - 声明一个块级作用域变量,可以选择赋值。(MDN)
  • const - 声明一个块级作用域变量,赋值一个不可重新赋值的值。(MDN)

在 Flow 中,它们分为两组

  • letvar - 可以重新赋值的变量。
  • const - 不可重新赋值的变量。
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

由于 const 变量在之后无法重新赋值,所以它相当简单。

Flow 可以从你为它赋值的值中推断出类型,或者你可以提供一个类型。

1const foo /* : number */ = 1;2const bar: number = 2;

varlet 0.186

由于 varlet 可以重新赋值,所以你需要了解一些额外的规则。

当你提供一个类型时,你将能够重新赋值,但它必须始终是兼容的类型。

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]

当变量没有注解时,Flow 会根据它们的初始化器或初始赋值推断出一个精确的类型。对该变量的所有后续赋值都将受此类型的约束。本节展示了 Flow 如何确定未注解变量被推断为具有什么类型的示例。

如果你希望一个变量具有与 Flow 为其推断的类型不同的类型,你始终可以在变量的声明中添加类型注解。这将覆盖本页中讨论的所有内容!

在声明时初始化的变量

未注解变量的常见情况非常简单:当一个变量在声明时使用一个非字面量 null 的初始化器进行初始化时,该变量从那时起将具有初始化器的类型,并且将来对该变量的写入将受该类型的约束。

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]

如果你希望这些示例通过类型检查,并且希望 Flow 意识到可以将不同类型的值写入这些变量,那么你必须在它们的声明中添加一个反映这种更通用类型的类型注解

let product: number | string = ...
let Component: mixed = ... // No good type to represent this! Consider restructuring
let element: React.Node = ...

未初始化的变量

变量通常在声明时没有初始化器。在这种情况下,Flow 将尝试选择对变量的“第一个”赋值或赋值来定义其类型。“第一个”在这里既指从上到下,也指从更近的作用域到更深的作用域——我们将尝试选择在与变量声明相同函数作用域中发生的赋值,并且只有在本地找不到任何赋值时才会查看嵌套函数内部

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]

如果由于 ifswitch 语句,存在两个或多个可能的“第一个赋值”,那么它们都将被计算在内——这是 Flow 为变量类型推断联合的少数几种方式之一

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.

但这仅适用于在两个分支中都写入变量的情况。如果只有一个分支包含写入,那么该写入将成为变量之后的类型(尽管 Flow 仍然会检查以确保变量肯定已初始化)

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]

初始化为 null 的变量

最后,变量类型由其第一个赋值(或赋值)决定的通用原则的一个例外是,当一个变量被初始化为(或其第一个赋值是)字面量值 null 时。在这种情况下,下一个非空赋值(使用与上面相同的规则)决定了变量类型的其余部分,并且变量的总体类型成为 null 和后续赋值类型的联合。这支持了变量在被其他类型的值赋值之前先为 null 的常见模式

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}

捕获变量 0.197

如果 catch 变量没有注解,则其默认类型为 any.

你可以选择用完全的 mixedany 对其进行注解。例如

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}

通过使用 mixed,你可以提高安全性并提高 Flow 的 覆盖率,但代价是增加运行时检查。

当没有注解时,你可以通过将 use_mixed_in_catch_variables 选项设置为 true 来更改 catch 变量的默认类型。