После нескольких ударов в голову, чтобы понять, что здесь происходит, у меня что-то есть.
Сначала я бы предложил немного ослабить ваш тип для handlers
, чтобы не требовалось , чтобы аргументы обработчика содержали дискриминант "kind"
, например:
interface CrmEventMap {
event1: { attr1: string; attr2: number };
event2: { attr3: boolean; attr4: string };
}
const handlers: {
[K in keyof CrmEventMap]: (e: CrmEventMap[K]) => Promise<void>
} = {
event1: ({ attr1, attr2 }) => Promise.resolve(),
event2: ({ attr3, attr4 }) => Promise.resolve()
};
Так что вам здесь вообще не нужно CrmEvent<K>
. Ваша возможная реализация handleEvent
должна будет использовать дискриминант, чтобы сообщить, как отправлять события, но вышеприведенное handlers
не имеет значения: каждая функция будет работать только с событием, которое уже было отправлено соответствующим образом. Вы можете оставить все то же самое, что и у вас, если хотите, но мне это не нужно.
Теперь для реализации eventAssigner
:
const eventAssigner = <
M extends Record<keyof M, (e: any) => any>,
D extends keyof any
>(
handlers: M,
discriminant: D
) => <K extends keyof M>(
event: Record<D, K> & (Parameters<M[K]>[0])
): ReturnType<M[K]> => handlers[event[discriminant]](event);
Итак, eventAssigner
является универсальной карри-функцией. Это универсальный тип в M
, тип handlers
(который у вас есть в качестве переменной handlers
), который должен быть объектом, содержащим свойства функции с одним аргументом, и D
, тип discriminant
(который у вас есть строка "kind"
), которая должна иметь правильный тип ключа. Затем он возвращает другую функцию, которая является общей в K
, предназначенную для использования в качестве одной из клавиш M
. Его параметр event
имеет тип Record<D, K> & (Parameters<M[K]>[0])
, что в основном означает, что это должен быть тот же аргумент типа, что и у K
-ключевого свойства M
, а также объект с ключом дискриминанта D
и значением K
. Это аналог вашего CrmEvent<K>
типа.
И он возвращает ReturnType<M[K]>
. Эта реализация не нуждается в утверждении типа только потому, что ограничение на M
имеет каждую функцию-обработчик extension (e: any)=>any
. Поэтому, когда компилятор проверяет handlers[event[discriminant]]
, он видит функцию, которую необходимо присвоить (e: any)=>any
, и вы можете в основном вызывать ее для любого аргумента и возвращать любой тип. Так что он с радостью позволил бы вам вернуться handlers[event[discriminant]]("whoopsie") + 15
. Так что вам нужно быть осторожным здесь. Вы можете обойтись без any
и использовать что-то вроде (e: never)=>unknown
, что будет безопаснее, но тогда вам придется использовать утверждение типа. Вам решать.
В любом случае, вот как вы его используете:
const handleEvent = eventAssigner(handlers, "kind");
Обратите внимание, что вы просто используете вывод общего типа и вам не нужно указывать там что-то вроде <CrmEventsMap>
. По моему мнению, использование вывода типа является более
«идеальный», чем ручное указание вещей. Если вы хотите указать что-то здесь, это должно быть eventAssigner<typeof handlers, "kind">(handlers, "kind")
, что глупо.
И убедившись, что он ведет себя так, как вы ожидаете:
const event1Response = handleEvent({ kind: "event1", attr1: "a", attr2: 3 }); // Promise<void>
const event2Response = handleEvent({ kind: "event2", attr3: true, attr4: "b" }); // Promise<void>
Хорошо выглядит. Хорошо, надеюсь, это поможет. Удачи!
Ссылка на код