跳至主要内容

常见问题解答

我检查了 foo.bar 不为 null,但 Flow 仍然认为它是。为什么会这样,我该如何解决?

Flow 不会跟踪副作用,因此任何函数调用都可能使您的检查失效。这被称为 细化失效。示例

1type Param = {2  bar: ?string,3}4function myFunc(foo: Param): string {5  if (foo.bar) {6    console.log("checked!");7    return foo.bar; // Flow errors. If you remove the console.log, it works
8 }9 10 return "default string";11}
7:12-7:18: Cannot return `foo.bar` because null or undefined [1] is incompatible with string [2]. [incompatible-return]

您可以通过将检查过的值存储在局部变量中来解决此问题

1type Param = {2  bar: ?string,3}4function myFunc(foo: Param): string {5  if (foo.bar) {6    const bar = foo.bar;7    console.log("checked!");8    return bar; // Ok!9  }10
11  return "default string";12}

我检查了我的值是 A 类型,那么为什么 Flow 仍然认为它是 A | B?

细化失效也可能发生在变量更新时

1type T = string | number;2
3let x: T = 'hi';4
5function f() {6  x = 1;7}8
9if (typeof x === 'string') {10  f();11  x as string;
12}
11:3-11:3: Cannot cast `x` to string because number [1] is incompatible with string [2]. [incompatible-cast]

解决方法是将变量设为 const 并重构代码以避免重新赋值

1type T = string | number;2
3const x: T = 'hi';4
5function f(x: T): number {6  return 1;7}8
9if (typeof x === 'string') {10  const xUpdated = f(x);11  xUpdated as number;12  x as string;13}

我在闭包中,Flow 忽略了断言 foo.bar 已定义的 if 检查。为什么?

在上一节中,我们展示了细化如何在函数调用后丢失。在闭包中也会发生完全相同的事情,因为 Flow 不会跟踪您的值在闭包被调用之前可能如何改变

1const people = [{age: 12}, {age: 18}, {age: 24}];2const oldPerson: {age: ?number} = {age: 70};3if (oldPerson.age) {4  people.forEach(person => {5    console.log(`The person is ${person.age} and the old one is ${oldPerson.age}`);
6 })7}
5:67-5:79: Cannot coerce `oldPerson.age` to string because null or undefined [1] should not be coerced. [incompatible-type]

这里的解决方案是在 forEach 中移动 if 检查,或者将 age 赋值给一个中间变量

1const people = [{age: 12}, {age: 18}, {age: 24}];2const oldPerson: {age: ?number} = {age: 70};3if (oldPerson.age) {4  const age = oldPerson.age;5  people.forEach(person => {6    console.log(`The person is ${person.age} and the old one is ${age}`);7  })8}

但是 Flow 应该理解这个函数不会使这个细化失效,对吧?

Flow 不是 完整的,因此它无法完美地检查所有代码。相反,Flow 会做出保守的假设,以尝试保持健壮性。

为什么我不能在 if 子句中使用函数来检查属性的类型?

Flow 不会跟踪在单独的函数调用中进行的 细化

1const add = (first: number, second: number) => first + second;2const val: string | number = 1;3const isNumber = (x: mixed): boolean => typeof x === 'number';4if (isNumber(val)) {5  add(val, 2);
6}
5:7-5:9: Cannot call `add` with `val` bound to `first` because string [1] is incompatible with number [2]. [incompatible-call]

但是,您可以使用 类型守卫 来注解您的函数,以获得这种行为

1const add = (first: number, second: number) => first + second;2const val: string | number = 1;3// Return annotation updated:4const isNumber = (x: mixed): x is number => typeof x === 'number';5if (isNumber(val)) {6  add(val, 2);7}

为什么我不能将 Array<string> 传递给接受 Array<string | number> 的函数?

函数的参数允许其数组中存在 string 值,但在这种情况下,Flow 会阻止原始数组接收 number。在函数内部,您将能够将 number 推入参数数组,导致原始数组的类型不再准确。

您可以通过将参数的类型更改为 $ReadOnlyArray<string | number> 来修复此错误,使其成为 协变。这将阻止函数体向数组中推送任何内容,允许它接受更窄的类型。

例如,这将不起作用

1const fn = (arr: Array<string | number>) => {2  arr.push(123); // Oops! Array<string> was passed in - now inaccurate3  return arr;4};5
6const arr: Array<string> = ['abc'];7
8fn(arr); // Error!
8:4-8:6: Cannot call `fn` with `arr` bound to `arr` because number [1] is incompatible with string [2] in array element. Arrays are invariantly typed. See https://flow.node.org.cn/en/docs/faq/#why-cant-i-pass-an-arraystring-to-a-function-that-takes-an-arraystring-number. [incompatible-call]

但是使用 $ReadOnlyArray,您可以实现您想要的结果

1const fn = (arr: $ReadOnlyArray<string | number>) => {2  // arr.push(321); NOTE! Since you are using $ReadOnlyArray<...> you cannot push anything to it3  return arr;4};5
6const arr: Array<string> = ['abc'];7
8fn(arr);

为什么我不能将 {a: string} 传递给接受 {a: string | number} 的函数?

函数参数允许其字段中存在 string 值,但在这种情况下,Flow 会阻止原始对象写入 number。在函数体内,您将能够修改对象,以便属性 a 接收 number,导致原始对象的类型不再准确。

您可以通过使属性成为 协变(只读)来修复此错误:{+a: string | number}。这将阻止函数体写入属性,使其可以安全地将更严格的类型传递给函数。

例如,这将不起作用

1const fn = (obj: {a: string | number}) => {2  obj.a = 123; // Oops! {a: string} was passed in - now inaccurate3  return obj;4};5
6const object: {a: string} = {a: 'str' };7
8fn(object); // Error!
8:4-8:9: Cannot call `fn` with `object` bound to `obj` because number [1] is incompatible with string [2] in property `a`. This property is invariantly typed. See https://flow.node.org.cn/en/docs/faq/#why-cant-i-pass-a-string-to-a-function-that-takes-a-string-number. [incompatible-call]

但是使用协变属性,您可以实现您想要的结果

1const fn = (obj: {+a: string | number}) => {2  // obj.a = 123 NOTE! Since you are using covariant {+a: string | number}, you can't mutate it3  return obj;4};5
6const object: {a: string} = { a: 'str' };7
8fn(object);

为什么我不能细化对象的联合?

有两个可能的原因

  1. 您正在使用不精确的对象。
  2. 您正在解构对象。在解构时,Flow 会丢失对对象属性的跟踪。

错误示例

1type Action =2  | {type: 'A', payload: string}3  | {type: 'B', payload: number};4
5// Not OK6const fn = ({type, payload}: Action) => {7  switch (type) {8    case 'A': return payload.length; // Error!
9 case 'B': return payload + 10;10 }11}
8:30-8:35: Cannot get `payload.length` because property `length` is missing in `Number` [1]. [prop-missing]

修复后的示例

1type Action =2  | {type: 'A', payload: string}3  | {type: 'B', payload: number};4
5// OK6const fn = (action: Action) => {7  switch (action.type) {8    case 'A': return action.payload.length;9    case 'B': return action.payload + 10;10  }11}

我遇到了“缺少类型注解”错误。它从哪里来?

Flow 要求在模块边界处进行类型注解,以确保它可以扩展。要了解更多信息,请查看我们的 博客文章

您会遇到的最常见情况是导出函数或 React 组件时。Flow 要求您注解输入。例如,在此示例中,Flow 会报错

1export const add = a => a + 1;
1:20-1:20: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type annotation at identifier: [signature-verification-failure]
1:20-1:20: Missing an annotation on `a`. [missing-local-annot]
1:21-1:20: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type annotation at function return: [signature-verification-failure]

这里的解决方法是为 add 的参数添加类型

1export const add = (a: number): number => a + 1;

要了解如何注解导出的 React 组件,请查看我们关于 HOC 的文档。

还有其他情况下会发生这种情况,它们可能更难理解。您会收到类似 缺少 U 的类型注解 的错误。例如,您编写了以下代码

1const array = ['a', 'b']2export const genericArray = array.map(a => a)
2:29-2:45: Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. [signature-verification-failure]

在这里,Flow 会在 export 上报错,要求进行类型注解。Flow 希望您注解由泛型函数返回的导出内容。Array.prototype.map 的类型是 map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U><U> 对应于所谓的 泛型,用于表达传递给 map 的函数的类型与数组的类型相关联的事实。

了解泛型背后的逻辑可能很有用,但您真正需要知道的是,您需要帮助 Flow 理解 genericArray 的类型。

您可以通过注解导出的常量来做到这一点

1const array = ['a', 'b']2export const genericArray: Array<string> = array.map(a => a)