Хотя проблема, точно такая, как указано, не имеет (я считаю) решения в
текущую версию Flow, я могу предоставить подход, который работает, если
Вы готовы предоставить Flow дополнительную информацию заранее.
Часть, которую я не могу понять, это то, могу ли я определить тип карты
такой, что ключ должен содержаться в типе значения?
Для подобных задач $ObjMap
обычно
инструмент, к которому вы хотите обратиться. Но $ObjMap
только трансформирует существующие
типы карт, и он только преобразует их значения (независимо от ключа), поэтому
здесь не совсем достаточно. Хитрость заключается в том, чтобы дать Flow отображение из
имя действия для типа действия явно:
type Actions /* helper type, never instantiated */ = {
FOO: FooAction,
BAR: BarAction,
};
type Action = $Values<Actions>; // = FooAction | BarAction;
Я полагаю, что это не слишком нежелательно, так как вы уже определяете
type Action
в вашем примере. Учитывая, что вы можете определить Action
в
условия Actions
, дополнительная избыточность невелика (по модулю
обсуждение ниже о связывании ключей с type
атрибутами - читайте дальше).
Учитывая такой тип карты действий, мы можем набрать createReducer
следующим образом:
type HandlerMap<O, S> = $Exact<$ObjMap<O, <A>(A) => (S, A) => S>>;
type Reducer<O, S> = (S, $Values<O>) => S;
declare var createReducer: <O, S>(
initialState: S,
handlers: HandlerMap<O, S>
) => Reducer<O, S>;
Вот пример использования:
type FooAction = { type: "FOO", payload: number };
type BarAction = { type: "BAR", payload: string };
type Actions /* helper type, never instantiated */ = {
FOO: FooAction,
BAR: BarAction,
};
const reducer = createReducer<Actions, $ReadOnlyArray<string>>(
([]: $ReadOnlyArray<string>),
{
FOO: (state, action) => [...state, action.payload.toFixed(0)],
BAR: (state, action) => [...state, action.payload],
// Flow will flag a type error if you uncomment the following line,
// complete with the helpful message "property `BAZ` is missing in
// object type but exists in object literal".
//BAZ: (state, action) => state,
}
);
let result: $ReadOnlyArray<string> = [];
result = reducer(result, { type: "FOO", payload: 6 });
result = reducer(result, { type: "BAR", payload: "six" });
// Each of the following lines flags a type error if uncommented.
//result = reducer(result, { type: "FOO", payload: "six" });
//result = reducer(result, { type: "BAR", payload: 6 });
//result = reducer(result, { type: "BAZ", payload: true });
Обратите внимание, что ключи типа Actions
не должны быть связаны в
любой путь к type
полям самих действий. Например, если
вместо этого мы использовали определения типа действия
type FooAction = { type: "ONE", payload: number };
type BarAction = { type: "TWO", payload: string };
type Actions /* helper type, never instantiated */ = {
FOO: FooAction,
BAR: BarAction,
};
тогда код все еще будет успешно проверяться, но, вероятно,
сбой во время выполнения (или, с другой точки зрения: невозможно
на самом деле реализовать createReducer
с вышеуказанными типами). Но не беспокойся,
потому что мы можем попросить Flow проверить и это условие:
// Check that each the `type` property of each value in the action-map
// corresponds to that action's key.
(function<K: $Keys<Actions>>(
x: $PropertyType<$ElementType<Actions, K>, "type">
): K {
return x;
});
Выше не удастся проверить, если вы случайно определите действие
у которого нет свойства type
или чье свойство type
не имеет
соответствуют его ключу в типе Actions
. Существование этого
Функция является доказательством правильности построения карты. *
В идеале, мы могли бы потребовать, чтобы это условие выполнялось для любого
карта действий O
, которая используется с createReducer
- мы должны иметь возможность
написать
type HandlerMapCorrectnessProof<O> = <K: $Keys<O>>(
$PropertyType<$ElementType<Actions, K>, "type">
) => K;
declare var createReducer: <O, S>(
initialState: S,
handlers: HandlerMap<O, S>,
proof: HandlerMapCorrectnessProof<O>
) => Reducer<O, S>;
и просто требуют, чтобы клиенты передавали (x) => x
в качестве третьего аргумента
createReducer
. Однако, если мы попытаемся сделать это, мы столкнемся с Flow
проблема № 7548 , так что сейчас вам придется полагаться на внешнее
проверка соответствия ваших карт действий (либо вручную)
осмотр или с помощью отдельных статических проверок, как описано выше).
Итак, вот полный пример, включающий статические
проверить .
Слово предупреждения: механизм, встроенный в этот ответ, сильно зависит от
манипулирование на уровне типа, которое значительно сложнее, чем большинство
Ежедневный код потока. Это имеет два важных значения: (а) это далеко
более вероятно попадание в странные баги в потоке (как # 7548 выше), и (б) это
может быть трудно понять для сотрудников, которые не очень хорошо разбираются в
Поток или не привык думать о типах таким образом. Лично я нахожу
что я получаю максимум от Flow, когда использую очень простое его подмножество,
в основном просто непересекающиеся объединения точных объектов только для чтения . я
представить этот пост в ответ на прямую просьбу ответить на этот
вопрос, но я бы не решился сам его запустить в производство. Тот
сказал, что это тема, по которой разумные люди могут разумно отличаться
по мнению.
* Это утверждение не совсем точно, чтобы быть правильным - то, что я
на самом деле это означает, что тот факт, что функция идентичности имеет этот тип
является доказательством, или другими словами, что идентичность
тип обитаем. Flow не имеет словарного запаса, чтобы правильно обсуждать
типы идентичности, но его формулировка границ типов достаточно сильна, чтобы
Я подозреваю, что любая функция этого типа, что текущая версия
Поток принимает, фактически должен быть функцией идентификации.