Поток путает тип обратного вызова свойства компонента React - PullRequest
0 голосов
/ 25 сентября 2019

Каким-то образом поток путает тип обратного вызова onValueChange компонента Picker.Это мои настройки:

Я определил тип состояния с помощью 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 ({пол: 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

Похоже, что поток думает, что itemvalue - это число, но вместо этого это строка, верно?

1 Ответ

1 голос
/ 25 сентября 2019

Давайте посмотрим на определение события onValueChange в компоненте Picker реагирующего типа , используемый здесь компонент:

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

Итак, тип первого аргументаобратного вызова является объединением string и number, string | number.Почему это?Что ж, если мы посмотрим на реализацию Picker, то увидим, что она построена с предположением, что значениями опций будут либо числа, либо строки .Это удобнее, чем, например, разрешать только строки, поскольку в некоторых случаях вам нужно будет преобразовывать строки в числа.Недостатком здесь является то, что каждый пользователь (который использует типы потока) вынужден обрабатывать как строковые, так и числовые случаи, даже когда они явно работают только в одном или другом типе.Это тип проблемы, который обычно можно решить с помощью обобщений, но его здесь не было, поэтому нам остается обработать оба случая.

Итак, давайте посмотрим на наш обработчик:

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

Прежде всего, давайте наберем аргумент правильно, чтобы у нас было ясное представление о том, с чем мы работаем:

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

Конечно, это все еще ошибки, потому что gender в состоянии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.К счастью, поток имеет встроенный специальный случай для этого типа функции, но как функцию с именем invariant.Мы можем реализовать нашу собственную функцию invariant, но мы, вероятно, должны просто использовать пакет npm, такой как this .Этот особый случай в потоке существует, потому что именно так обрабатываются инварианты в кодовой базе реагирования и на Facebook в более общем плане.Итак, как выглядит наш обработчик с invariant?

import invariant from 'invariant';

// ...

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

  this.setState({ gender: itemvalue, isDirty: true });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...