Ответ 1
Канонический ответ на этот вопрос зависит от вашего конкретного случая использования. Я собираюсь предположить, что вам нужно Action
точно оценить тип, который вы написали; то есть объект type: "DO_X"
не имеет свойства payload
любого типа. Это означает, что createAction("DO_X")
должна быть функцией от нулевых аргументов, а createAction("DO_Y")
должна быть функцией одного аргумента string
. Я также предполагаю, что вы хотите, чтобы любые параметры типа на createAction()
были автоматически выведены, поэтому вам не нужно, например, указывать createAction<Blah>("DO_Z")
для любого значения Blah
. Если какое-либо из этих ограничений отменено, вы можете упростить решение для чего-то вроде @Arnavion.
TypeScript не любит отображать типы из значений свойств, но он счастлив сделать это из ключей свойств. Поэтому давайте построим тип Action
таким образом, чтобы предоставить нам типы, которые компилятор может нам помочь. Сначала мы описываем полезную нагрузку для каждого типа действия следующим образом:
type ActionPayloads = {
DO_Y: string;
DO_Z: number;
}
Пусть также ввести любые типы Action
без полезной нагрузки:
type PayloadlessActionTypes = "DO_X" | "DO_W";
(Я добавил тип 'DO_W'
, чтобы показать, как он работает, но вы можете удалить его).
Теперь мы наконец можем выразить Action
:
type ActionMap = {[K in keyof ActionPayloads]: { type: K; payload: ActionPayloads[K] }} & {[K in PayloadlessActionTypes]: { type: K }};
type Action = ActionMap[keyof ActionMap];
Тип ActionMap
- это объект, чьи ключи являются type
каждого Action
, а значениями являются соответствующие элементы объединения Action
. Это пересечение Action
с payload
s, а Action
без payload
s. И Action
- это только тип значения ActionMap
. Убедитесь, что Action
- это то, что вы ожидаете.
Мы можем использовать ActionMap
, чтобы помочь нам ввести функцию createAction()
. Вот он:
function createAction<T extends PayloadlessActionTypes>(type: T): () => ActionMap[T];
function createAction<T extends keyof ActionPayloads>(type: T): (payload: ActionPayloads[T]) => ActionMap[T];
function createAction(type: string) {
return (payload?: any) => (typeof payload === 'undefined' ? { type } : { type, payload });
}
Это перегруженная функция с параметром типа T
, соответствующим type
Action
, который вы создаете. В двух верхних объявлениях описываются два случая: Если T
является type
для Action
без payload
, возвращаемый тип - это функция с нулевым аргументом, возвращающая правильный тип Action
. В противном случае это функция с одним аргументом, которая принимает правильный тип payload
и возвращает правильный тип Action
. Реализация (третья подпись и тело) похожа на вашу, за исключением того, что она не добавляет payload
к результату, если не передано payload
.
Все сделано! Мы видим, что он работает по желанию:
var x = createAction("DO_X")(); // x: { type: "DO_X"; }
var y = createAction("DO_Y")("foo"); // y: { type: "DO_Y"; payload: string; }
var z = createAction("DO_Z")(5); // z: { type: "DO_Z"; payload: number; }
createAction("DO_X")('foo'); // too many arguments
createAction("DO_X")(undefined); // still too many arguments
createAction("DO_Y")(5); // 5 is not a string
createAction("DO_Z")(); // too few arguments
createAction("DO_Z")(5, 5); // too many arguments
Вы можете увидеть все это в действии на TypeScript игровой площадке. Надеюсь, это сработает для вас. Удачи!