我有一个场景,我有以下不同的事件:
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]
,这是EventMap
在K
键处的值类型。
让我们测试一下:
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
}