首页 > 解决方案 > Flow 混淆了 React 组件属性回调的类型

问题描述

不知何故,流程混淆onValueChange了 Picker 组件的回调类型。这是我的设置:

我在其中将 State 类型定义gender为字符串:

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

在 Picker 组件中,我想在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)

我还尝试像这样键入定义 itemvalue:

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

但这只是将错误移至新添加的string

看起来 flow 认为 itemvalue 是一个数字,但它是一个字符串,对吗?

标签: javascriptreact-nativeflowtype

解决方案


我们看一下react-native Picker 组件上的事件定义,onValueChange这里使用的组件:

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

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

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

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

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

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

当然这仍然是错误的,因为gender状态是 a string,但至少我们现在可以非常清楚地看到为什么会发生这种情况。

在我们的实现中,我们知道itemvalue将永远是一个字符串,否则某些东西会以某种基本方式严重损坏。我们只提供字符串选项值,所以如果我们得到一个数字,那么绝对可怕的事情就发生了。这种具体的期望通常被称为“不变量”。在我们的例子中,它是一个不itemvalue变量string

那么我们该怎么办呢?通常当你有多个类型的联合并且你想处理联合中不同类型的各种情况时,你会使用类型细化。所以是这样的:

(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函数返回,itemvalue就会回到string | number. 幸运的是,flow 为这种类型的函数有一个内置的特殊情况,但作为一个名为invariant. 我们可以实现自己的invariant功能,但我们可能应该只使用这样的 npm。存在流中的这种特殊情况是因为它是在反应代码库中以及更普遍地在 facebook 中处理不变量的方式。那么,我们的处理程序是什么样的invariant呢?

import invariant from 'invariant';

// ...

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

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

推荐阅读