给定类型事件的有效负载类型推断



我有一个场景,我有以下不同的事件:

type EventType = 'foo' | 'bar'
type Event = {
type: 'foo',
timestamp: number,
payload: { a: number }
} | {
type: 'bar',
timestamp: number,
payload: { b: string }
}

然后我有一个这样的监听器:

on('foo', event => {
// Here we should know that the type must be 'foo',
// and therefore the payload has the shape {a: number}
// but I don't know how to tell that to TS
})

我尝试了一些不同的事情,但到目前为止,我所管理的是编译器停止编译🥲

我认为这个问题会有帮助,但我没有设法使它工作。我认为问题在于我使用了一个字面值联合而不是enum。

我想这种情况在很多地方都有发生,所以我希望能找到一个更容易的解决方案。

我的建议是将on()作为其第一个参数的K类型的泛型函数,然后根据该类型编写event回调参数的类型。

为此,我将首先编写一个名为EventMap的助手类型,如下所示:

type EventMap = { [E in Event as E['type']]: E };

Event重新映射为一个对象类型,该对象类型的键是on()的第一个参数,其值是event回调参数的类型,如下所示:

/* type EventMap = {
foo: {
type: 'foo';
timestamp: number;
payload: {
a: number;
};
};
bar: {
type: 'bar';
timestamp: number;
payload: {
b: string;
};
};
} */
有了这个类型,on()的呼叫签名可以写成:

declare function on<K extends keyof EventMap>(
type: K,
cb: (event: EventMap[K]) => void
): void;

因此,event回调参数的索引访问类型是EventMap[K],这是EventMapK键处的值类型。

让我们测试一下:

on('foo', event => {
event.payload.a.toFixed(2); // okay
});
on('bar', event => {
event.payload.b.toUpperCase(); // okay
});

看起来不错!

Playground链接到代码

if (event.type == 'foo') {
// event is Extract<event, {type: 'foo'}>
event.payload.a
} else {
// event is Exclude<event, {type: 'foo'}>
event.payload.b
}

最新更新