Это не правильный ответ, так как это альтернатива. Мне не удалось заставить ваш пример работать, и в целом мне было сложно работать с типами объектов и спредами в Flow, поэтому я решил эту проблему. Это упрощенный пример, включающий достаточно действий, чтобы показать используемый стиль.
type Dispatch<A, B> = A => B;
class AddToCartAction {
payload: number;
constructor(payload: number) {
this.payload = payload;
}
}
class RemoveFromCartAction {
payload: number;
constructor(payload: number) {
this.payload = payload;
}
}
class UpdateTotalsAction {}
/*
* I removed the additional type annotations from the constants here,
* and put their literal values in ProductActionsType instead. While
* this adds some duplication, it works alongside Flow better. If the
* annotations are used on the constants, and anyone forgets to add one
* to a new constant, then ProductActionsType will have its type inferred
* as "string", thus invisibly reducing type safety by allowing more
* inputs than desired. By putting the literal strings in both the
* constants and the types, the type system will catch you if you make
* a typo, and it becomes more clear what is allowed as a
* ProductActionsType.
*/
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const UPDATE_TOTALS = "UPDATE_TOTALS";
export type ProductActionsType =
| "ADD_TO_CART"
| "REMOVE_FROM_CART"
| "UPDATE_TOTALS";
export type ProductActionsReturnTypes =
| AddToCartAction
| RemoveFromCartAction
| UpdateTotalsAction;
export const updateCart = (id: number, type: ProductActionsType) => {
return (dispatch: Dispatch<ProductActionsReturnTypes, void>) => {
const action =
type === "ADD_TO_CART" ? new AddToCartAction(id)
: type === "REMOVE_FROM_CART" ? new RemoveFromCartAction(id)
: /* else UPDATE_TOTALS */ new UpdateTotalsAction();
dispatch(action);
dispatch(new UpdateTotalsAction());
};
};
Основная идея c заключается в том, что вместо использования объектов с полем type
один класс сделано для каждого типа. Уточнения типов потока очень и очень хороши при работе с классами, и легче понять сигнатуры типов и ошибки типов, когда несколько типов не объединяются в один.
Вложенные троичные выражения можно прочитать как альтернативу оператор switch / case, с дополнительным преимуществом, что они могут обрабатывать произвольные логические выражения в своем состоянии. Например, я бы обработал действия с кодом, подобным следующему:
function handleActions(a: ProductActionTypes) {
return a instanceof AddToCartAction ? handleAddToCart(a.payload)
: a instanceof RemoveFromCartAction ? handleRemoveFromCart(a.payload)
: /* a is an UpdateTotalsAction */ handleUpdateTotals();
}
Этот синтаксис можно увидеть в разделе «Условные цепочки» Документы MDN для троичных выражений. Он хорошо работает с Flow, который будет знать тип a
в каждой правой части условных выражений. Flow даже узнает, если вы забыли проверить случай, поскольку последнее условное условие предполагает, что все остальные классы типа ProductActionTypes
уже проверены.
Надеюсь, это полезно, даже если это не так. т именно то, что вы искали. Я пишу весь свой код Flow в этом стиле, и с тех пор, как я начал это делать, у меня не было действительно грубых, запутанных ошибок типов.