Давайте посмотрим на определение события 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 });
}