类型与表达式
在 JavaScript 中,有许多类型的值:数字、字符串、布尔值、函数、对象等等。
11234 as number;2"hi" as string;3true as boolean;4[1, 2] as Array<number>;5({prop: "value"}) as {prop: string};6(function func(s: string) {}) as string => void;
这些值可以以多种不同的方式使用
11 + 2;2"foo" + "bar";3!true;4[1, 2].push(3);5const obj = {prop: "s"};6let value = obj.prop;7obj.prop = "value";8function func(s: string) {}9func("value");
所有这些不同的表达式都会创建一个新的类型,该类型是值类型和对其运行的操作的结果。
1let num: number = 1 + 2;2let str: string = "foo" + "bar";
在 Flow 中,每个值和表达式都有一个类型。
静态地推断类型
Flow 需要一种方法来推断每个表达式的类型。但它不能仅仅运行你的代码来推断类型,如果这样做,它将受到你的代码中任何问题的影響。例如,如果你创建了一个无限循环,Flow 将永远等待它完成。
相反,Flow 需要能够通过分析而不运行代码来推断值的类型(静态分析)。它会遍历每个已知的类型,并开始推断围绕它们的表达式所产生的结果。
例如,为了推断以下表达式的结果,Flow 需要首先推断其值是什么。
val1 + val2;
如果值是数字,那么表达式将产生一个数字。如果值是字符串,那么表达式将产生一个字符串。这里有很多不同的可能性,因此 Flow 必须查找这些值是什么。
如果 Flow 无法推断出每个值的精确类型,Flow 必须推断出每个可能的类型,并检查周围的代码是否仍然可以与所有可能的类型一起使用。
健全性和完整性
当你运行你的代码时,单个表达式只会使用有限的一组值运行。但 Flow 仍然会检查所有可能的类型。这样,Flow 会检查太多东西,或者过度近似哪些代码是有效的。
通过检查所有可能的类型,Flow 可能会捕获在运行代码时实际上不会发生的错误。Flow 这样做是为了保持“健全性”。
在类型系统中,健全性是指类型检查器能够捕获在运行时可能发生的每个错误的能力。这以有时捕获在运行时实际上不会发生的错误为代价。
另一方面,完整性是指类型检查器只捕获在运行时会发生的错误的能力。这以有时错过在运行时会发生的错误为代价。
在理想情况下,每个类型检查器都应该是健全的和完整的,这样它就能捕获在运行时会发生的每个错误。
Flow 尽力做到尽可能健全和完整。但由于 JavaScript 不是围绕类型系统设计的,Flow 有时不得不做出权衡。当这种情况发生时,Flow 倾向于将健全性置于完整性之上,确保代码没有错误。
只要 Flow 不太吵,不会妨碍你的工作效率,健全性就可以了。有时,当健全性会过分妨碍你的工作效率时,Flow 会优先考虑完整性。Flow 这样做的情况很少。
其他类型系统会优先考虑完整性,只报告真正的错误,以换取可能错过的错误。单元/集成测试是这种方法的极端形式。通常,这会导致错过最难发现的错误,将这部分留给开发人员处理。