Typescript 为处理程序赋值器函数创建一个类型良好的泛型事件

Typescript 为处理程序赋值器函数创建一个类型良好的泛型事件,typescript,types,functional-programming,typescript-generics,Typescript,Types,Functional Programming,Typescript Generics,这是一个可重用机制的延续,该机制允许我们将传入事件(消息)分配给适当的事件处理程序,并在整个过程中完全依赖于类型。以下是我们想要使其可重复使用的内容: const handleEvent= (e:事件):承诺=>{ 常数种类:EventKind=e.kind; const handler=Promise>handlers[kind];//注意似乎不必要的断言。这就是我们将此函数设置为泛型函数的原因。 返回处理程序(e); }; 我希望我们在这里结束: const handleEvent = e

这是一个可重用机制的延续,该机制允许我们将传入事件(消息)分配给适当的事件处理程序,并在整个过程中完全依赖于类型。以下是我们想要使其可重复使用的内容:

const handleEvent=
(e:事件):承诺=>{
常数种类:EventKind=e.kind;
const handler=Promise>handlers[kind];//注意似乎不必要的断言。这就是我们将此函数设置为泛型函数的原因。
返回处理程序(e);
};
我希望我们在这里结束:

const handleEvent = eventAssigner<CrmEventsMap>(handlers, 'kind');
从中,我们可以创建完整的事件类型(包括鉴别器):

typecrmevent={kind:K}&EventsMap[K]
现在,我们拥有了声明处理程序映射所需的一切:

const处理程序:{[K in keyof CrmEventsMap]:(e:CrmEvent)=>Promise}={
event1:({attr1,attr2})=>Promise.resolve(),
事件2:({attr3,attr4})=>Promise.resolve(),
};
这让我们回到
handleEvent
。正文中的类型断言似乎是尝试使函数泛型的充分理由

以下是一个尝试:

const eventAssigner=
(
处理程序:{[k in keyof EventMap]:(e:EventType)=>any},
金菲尔德:金菲尔德
) =>
(e:EventType):
ReturnType=>{
常数kind=e[kindField];
const handler=ReturnType>handlers[kind];
返回处理程序(e);
};
类型EventType=
{[k in KindField]:Kind}&EventMap[Kind]
它很复杂,即使是在它的使用。但是,只要将events discriminator字段改为
“kind”
,我们就可以大大简化:

const eventAssigner =
  <EventMap extends {},
    EventKind extends keyof EventMap>
  (handlers: { [k in keyof EventMap]: (e: EventType<EventMap, k>) => any }) =>
    (e: EventType<EventMap, EventKind>):
      ReturnType<(typeof handlers)[EventKind]> =>
      handlers[e.kind](e);

type EventType<EventMap extends {}, Kind extends keyof EventMap> = { kind: Kind } & EventMap[Kind]

const eventAssigner=
(处理程序:{[k in keyof EventMap]:(e:EventType)=>any})=>
(e:EventType):
ReturnType=>
处理者[e.kind](e);
类型EventType={kind:kind}&EventMap[kind]
这一点特别有趣的是,由于某种原因,我无法解释,我们不需要类型断言

不过,要使这两个函数中的任何一个正常工作,都需要为它们提供具体的类型参数,这意味着要将它们包装到另一个函数中:

const handleEvent = 
  <E extends CrmEventKind>
  (e: CrmEvent<E>): ReturnType<(typeof handlers)[E]> => 
    eventAssigner<CrmEventMap, E>(handlers)(e);
const handleEvent=
(e:CrmEvent):返回类型=>
事件指派人(处理人)(e);
简而言之,您认为我们离理想的实现有多近


为了了解这里发生了什么,我在头上打了几下,我有了一些想法

首先,我建议稍微放宽
处理程序
的类型,这样就不需要处理程序参数具有
“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()
};
因此,
eventAssigner
是一个通用函数。它在
M
中是通用的,它是
处理程序的类型(作为变量
处理程序
),必须是一个包含一个参数函数属性的对象;在
D
中,它是
判别式
的类型(作为字符串
“kind”
),它必须是一个有效的键类型。然后,它返回另一个在
K
中通用的函数,该函数是
M
的键之一。它的
事件
参数的类型为
记录和(参数[0])
,这基本上意味着它必须是与
M
K
键控属性相同的类型参数,以及具有判别键
D
和值
K
的对象。这是您的
CrmEvent
类型的模拟

它返回
ReturnType
。此实现不需要类型断言,因为
M
上的约束使每个处理程序函数扩展
(e:any)=>any
。因此,当编译器检查
处理程序[event[discriminant]]
时,它会看到一个必须可分配给
(e:any)=>any
的函数,您基本上可以在任何参数上调用它并返回任何类型。因此,它很乐意让您返回
处理程序[event[discriminant]](“whoopsie”)+15
。所以你在这里要小心。您可以省去
any
,而使用类似
(e:never)=>unknown
的东西,这样会更安全,但是您必须使用类型断言。这取决于你

无论如何,下面是您如何使用它:

const handleEvent = eventAssigner(handlers, "kind");
请注意,您只是使用泛型类型推断,不必在其中指定任何类似
的内容。在我看来,使用类型推断更有效 “理想”而不是手动指定东西。如果您想在这里指定某个内容,它必须是
eventAssigner(handlers,“kind”)
,这很愚蠢

并确保其行为符合您的预期:

const event1Response = handleEvent({ kind: "event1", attr1: "a", attr2: 3 }); // Promise<void>
const event2Response = handleEvent({ kind: "event2", attr3: true, attr4: "b" }); // Promise<void>
const event1Response=handleEvent({kind:“event1”,attr1:“a”,attr2:3});//允诺
const event2Response=handleEvent({kind:“event2”,attr3:true,attr4:“b”});//允诺
看起来不错。好的,希望能有帮助。祝你好运


嗯,我觉得这个问题很难理解。当前实施的完整副本将有所帮助。我试着把代码复制粘贴到TS操场上,但没有成功。@NiiloKeinänen thanx指出了这一点。添加了一个游乐场,但也修复了问题中的几个错误。通过进一步改进实现,您的意思是缩短“handleEvent”和“eventAssigner”的定义,还是改进调用“handleEvent”的接口?目前我们需要做:handleEvent({kind:'event1',attr1:'hello',attr2:0})我想如果事件签名是唯一的,那么省略'kind'属性是合乎逻辑的。@NiiloKeinänen
kind
是必需的,因为它是唯一一个完全区分一个事件和另一个事件的字段。我的目标是“改进调用
eventAssigner
的接口”,使其看起来尽可能接近:
eventAssigner(handlers,'kind')
它太漂亮了,让我热泪盈眶。我不希望那样
const event1Response = handleEvent({ kind: "event1", attr1: "a", attr2: 3 }); // Promise<void>
const event2Response = handleEvent({ kind: "event2", attr3: true, attr4: "b" }); // Promise<void>