Skip to main content

常见问题

我查了一下,foo.bar 不是 null,但 Flow 仍然认为是。为什么会发生这种情况以及如何解决它?

¥I checked that foo.bar is not null, but Flow still thinks it is. Why does this happen and how can I fix it?

Flow 不会跟踪副作用,因此任何函数调用都可能使你的检查无效。这就是所谓的 细化失效。示例:

¥Flow does not keep track of side effects, so any function call may potentially nullify your check. This is called refinement invalidation. Example:

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 works8  }9
10  return "default string";11}

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

¥You can get around this by storing your checked values in local variables:

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?

¥I checked that my value is of type A, so why does Flow still believe it's A | B?

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

¥Refinement invalidation can also occur variables are updated:

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}

解决方法是使用变量 const 并重构代码以避免重新分配:

¥A work around would be to make the variable const and refactor your code to avoid the reassignment:

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 检查。为什么?

¥I'm in a closure and Flow ignores the if check that asserts that foo.bar is defined. Why?

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

¥In the previous section we showed how refinements are lost after a function call. The exact same thing happens within closures, since Flow does not track how your value might change before the closure is called:

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}

这里的解决方案是将 if 检查移到 forEach 中,或者将 age 分配给中间变量:

¥The solution here is to move the if check in the forEach, or to assign the age to an intermediate variable:

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 应该明白这个函数不能使这个细化失效,对吗?

¥But Flow should understand that this function cannot invalidate this refinement, right?

Flow 不是 完全的,所以它不能完美地检查所有代码。相反,Flow 会做出保守的假设以尽量保持合理。

¥Flow is not complete, so it cannot check all code perfectly. Instead, Flow will make conservative assumptions to try to be sound.

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

¥Why can't I use a function in my if-clause to check the type of a property?

Flow 不跟踪在单独的函数调用中进行的 改进

¥Flow doesn't track refinements made in separate function calls:

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}

但是,你可以使用 类型保护 注释你的函数来获得此行为:

¥However, you can annotate your function with a type guard to get this behavior:

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> 的函数

¥Why can't I pass an Array<string> to a function that takes an Array<string | number>

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

¥The function's argument allows string values in its array, but in this case Flow prevents the original array from receiving a number. Inside the function, you would be able to push a number to the argument array, causing the type of the original array to no longer be accurate.

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

¥You can fix this error by changing the type of the argument to $ReadOnlyArray<string | number>, making it covariant. This prevents the function body from pushing anything to the array, allowing it to accept narrower types.

举个例子,这是行不通的:

¥As an example, this would not work:

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!

但通过 $ReadOnlyArray,你可以实现你所寻求的:

¥but with $ReadOnlyArray you can achieve what you were looking for:

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} 的函数

¥Why can't I pass {a: string} to a function that takes {a: string | number}

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

¥The function argument allows string values in its field, but in this case Flow prevents the original object from having a number written to it. Within the body of the function you would be able to mutate the object so that the property a would receive a number, causing the type of the original object to no longer be accurate.

你可以通过将属性设置为 协变量(只读)来修复此错误:{+a: string | number}。这可以防止函数体写入属性,从而可以安全地将更受限制的类型传递给函数。

¥You can fix this error by making the property covariant (read-only): {+a: string | number}. This prevents the function body from writing to the property, making it safe to pass more restricted types to the function.

举个例子,这是行不通的:

¥As an example, this would not work:

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!

但通过协变属性,你可以实现你想要的目标:

¥but with a covariant property you can achieve what you were looking for:

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);

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

¥Why can't I refine a union of objects?

有两个潜在的原因:

¥There are two potential reasons:

  1. 你正在使用不精确的对象。

    ¥You are using inexact objects.

  2. 你正在解构该对象。解构时,Flow 会丢失对象属性的跟踪。

    ¥You are destructuring the object. When destructuring, Flow loses track of object properties.

损坏的示例:

¥Broken example:

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}

固定示例:

¥Fixed example:

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}

我收到 "缺少类型注释" 错误。它从何而来?

¥I got a "Missing type annotation" error. Where does it come from?

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

¥Flow requires type annotations at module boundaries to make sure it can scale. To read more about that, check out our blog post about that.

最常见的情况是导出函数或 React 组件时。Flow 要求你对输入进行注释。例如在这个例子中,Flow 会诉说:

¥The most common case you'll encounter is when exporting a function or React component. Flow requires you to annotate inputs. For instance in this example, Flow will complain:

1export const add = a => a + 1;

这里的修复方法是向 add 的参数添加类型:

¥The fix here is to add types to the parameters of add:

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

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

¥To see how you can annotate exported React components, check out our docs on HOCs.

还有其他情况会发生这种情况,并且可能更难以理解。你将收到类似 Missing type annotation for U 的错误 例如,你编写了以下代码:

¥There are other cases where this happens, and they might be harder to understand. You'll get an error like Missing type annotation for U For instance, you wrote this code:

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

在这里,Flow 将诉说 export,要求类型注释。Flow 希望你注释通用函数返回的导出。Array.prototype.map 的类型是 map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U><U> 对应于所谓的 泛型,表示传递给 map 的函数类型与数组类型相关联。

¥Here, Flow will complain on the export, asking for a type annotation. Flow wants you to annotate exports returned by a generic function. The type of Array.prototype.map is map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U>. The <U> corresponds to what is called a generic, to express the fact that the type of the function passed to map is linked to the type of the array.

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

¥Understanding the logic behind generics might be useful, but what you really need to know to make your typings valid is that you need to help Flow to understand the type of genericArray.

你可以通过注释导出的常量来做到这一点:

¥You can do that by annotating the exported constant:

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