Flow 混淆了 React 组件的类型 属性 回调

Flow confuses type of React component property callback

流程以某种方式混淆了 Picker 组件的 onValueChange 回调类型。这是我的设置:

我用 gender 定义了 State 类型作为其中的一个字符串:

type State = {
  gender: string,
  weight: number
// code omitted

};

在构造函数中初始化(这不是强制性的,不是吗?)

export default class CustomComponent extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { gender: '', weight: 0 };
  }

  render() {

    const { gender, weight } = this.state;

// code omitted

在选择器组件中,我想在 onValueChange 回调中设置性别状态,如下所示:

 <Picker
   style={styles.inputElement}
   selectedValue={gender}
   onValueChange={itemvalue => {
   this.setState({ gender: itemvalue, isDirty: true });
    }}
     > // code omitted

但是 Flow 在 itemvalue 处显示错误:this.setState({ gender: itemvalue, isDirty: true })

Cannot call `this.setState` with object literal bound to `partialState` because  number [1] is incompatible with  string [2].Flow(InferError)

我也试过像这样输入定义项目值:

onValueChange={(itemvalue: string) => ....}

但这只是移动了新添加的错误 string

看起来流认为 itemvalue 是一个数字,但实际上它是一个字符串,对吗?

我们来看the definition of the onValueChange event on the react-native Picker component,这里使用的组件:

onValueChange?: ?(itemValue: string | number, itemIndex: number) => mixed,

所以回调的第一个参数的类型是stringnumberstring | number的并集。为什么是这样?好吧,如果我们看一下 Picker 的实现,我们会发现它是在 the option values will either be numbers or strings 的假设下构建的。这比只允许字符串更方便,因为在某些情况下您需要将字符串转换为数字。这里的缺点是每个用户(使用流类型的人)都被迫处理字符串和数字的情况,即使他们显然只在一种或另一种类型中工作。这种问题通常可以使用泛型来解决,但还没有出现过,所以我们不得不处理这两种情况。

那么让我们看看我们的处理程序:

itemvalue => {
   this.setState({ gender: itemvalue, isDirty: true });
}

首先,让我们正确输入参数,以便清楚我们正在使用什么:

(itemvalue: string | number) => {
   this.setState({ gender: itemvalue, isDirty: true });
}

这当然还是会报错,因为gender中的状态是string,但至少我们现在可以很清楚地看到为什么会这样了。

在我们的实现中,我们知道 itemvalue 将永远是一个字符串,否则某些东西在某些基本方面会变得非常糟糕。我们只提供字符串选项值,所以如果我们得到一个数字,那么就发生了非常糟糕的事情。这种具体的期望通常称为 "invariant." 在我们的例子中,itemvaluestring.

的不变量

那我们该怎么办呢?通常当你有一个多类型的联合,你想处理联合中不同类型的各种情况时,you're going to use type refinement。所以像这样:

(itemvalue: string | number) => {
  if (typeof itemvalue === 'string') {
    (itemvalue: string); // do something with the string case
  } else {
    (itemvalue: number); // do something with the number case
  }
}

因此,一种可能的解决方案是忽略数字大小写,因为它实际上永远不会出现:

(itemvalue: string | number) => {
  if (typeof itemvalue === 'string') {
    this.setState({ gender: itemvalue, isDirty: true });
  }
}

这应该可以正常工作,但它只是忽略 number 情况有点奇怪,该输入来自某个地方,可能应该以某种方式处理。那么当违反不变量时会发生什么?也就是说,如果我们收到一个数字(应该不可能的事情),我们的回调实际上应该做什么

一般情况下,当违反不变量时,常见的行为是抛出异常,异常也可以用于流程中的类型细化:

(itemvalue: string | number) => {
  if (typeof itemvalue === 'number') throw new Error('itemvalue should be a string!');

  this.setState({ gender: itemvalue, isDirty: true });
}

但实际上我们应该在一般情况下解决这个问题,当然我们会 运行 在一个复杂的代码库中不止一个地方解决这个问题。如果我们将其提取到某种断言函数中会怎样?

const assert = (check: boolean, message: string) => {
  if (!check) {
    throw new Error(message);
  }
};

(itemvalue: string | number) => {
  assert(typeof itemvalue === 'number', 'itemvalue should be a string!');

  this.setState({ gender: itemvalue, isDirty: true }); // error!
}

现在我们的错误又回来了,因为流中的类型细化只在块范围内存在。一旦我们的 assert 函数 returns,itemvalue 就变回 string | number。幸运的是,flow 有一个针对此类函数的内置特例,但作为一个名为 invariant 的函数。我们可以实现我们自己的 invariant 功能,但我们应该只使用像 this 这样的 npm 包。流程中存在这种特殊情况,因为它是在 React 代码库和更普遍的 Facebook 中处理不变量的方式。那么,使用 invariant 时我们的处理程序是什么样的?

import invariant from 'invariant';

// ...

(itemvalue: string | number) => {
  invariant(typeof itemvalue === 'number', 'itemvalue should be a string!');

  this.setState({ gender: itemvalue, isDirty: true });
}