推断打字稿中的映射参数类型

时间:2019-11-08 01:48:51

标签: typescript type-inference

我正在使用一个有趣的类型声明(简化)的库:

class Emitter {
    public on<EventName extends 'streamCreated' | 'streamDestroyed'> (eventName: EventName, callback: (event: {
        streamCreated: {id: number};
        streamDestroyed: {reason: string};
    }[EventName]) => void): void;
}

我正在尝试在提供的回调中键入事件:

const callback = (event: StreamDestroyedEvent) => console.log(event.reason);
emitter.on('streamDestroyed', callback);

但是“ StreamDestroyedEvent”不存在。它不是由库提供的,而是仅存在于该匿名事件映射中,所以我改为尝试推断它:

type InferCallback<T, E> = T extends (eventName: E, event: infer R) => void ? R : never;
type InferCallbackEvent<T, E> = InferCallback<T, E> extends (event: infer P) => void ? P : never;
type StreamDestroyedEvent = InferCallbackEvent<Emitter['on'], 'streamDestroyed'>;

但是,没有给我类型{reason: string},而是得到了联合类型{reason: string;} | {id: number;}。如何获得正确的类型,或者这与我将要获得的接近?

1 个答案:

答案 0 :(得分:1)

您基本上希望为EventName类型参数“插入”一种类型。尽管调用泛型函数时TypeScript支持此功能,但在类型系统本身中表示这样的类型并不能很好地完成。要完全这样做,将需要对higher rank types的支持,而这实际上并不是该语言的一部分。并且emitter.on函数本身并不能真正让您“部分地”调用它,因此您要使用第一个参数插入EventName,然后让编译器告诉 you 第二个参数的类型应该是

在TypeScript 3.4发布之前,我可能会说不可能从编译器中获取此信息。我会尝试类似的

emitter.on("streamDestroyed", x => {
  type StreamDestroyedEvent = typeof x; // can't get it out of the scope though!
})

,然后无法获取StreamDestroyedEvent类型以转义该函数。

TypeScript 3.4引入了改进的支持higher order type inference from generic functions。您仍然不能仅表示类型系统中正在执行的操作,但是现在我们可以定义一个函数,该函数为我们提供可以“部分”调用并保留所需类型信息的功能。

这是一个currying函数,它接受一个多参数函数,并返回一个参数的新函数,该函数返回其余参数的另一个函数:

const curry = <T, U extends any[], R>(
  cb: (t: T, ...args: U) => R
) => (t: T) => (...args: U) => cb(t, ...args);

如果您在curry上调用emitter.on,然后用"streamDestroyed"调用 that ,(在TS3.4 +中)您将获得一个函数,该函数需要一个具有StreamDestroyedEvent的回调,您现在可以捕获它:

const streamDestroyedFunc = curry(emitter.on.bind(emitter))("streamDestroyed")
type StreamDestroyedEvent = Parameters<Parameters<typeof streamDestroyedFunc>[0]>[0];
// type StreamDestroyedEvent = { reason: string; }

请注意,以上内容的实际运行时行为并非重点;我试图确保它实际上会做一些合理的事情,但是只要您在运行时不会遇到运行时错误,您也可以使用类型断言来向编译器说谎,以了解发生了什么事情:

const fakeCurry: typeof curry = () => () => null!;
const fakeOn: Emitter["on"] = null!;
const fakeFunc = fakeCurry(fakeOn)("streamDestroyed");
type SDE2 = Parameters<Parameters<typeof fakeFunc>[0]>[0]; // same thing

好的,希望能有所帮助;祝你好运!

Link to code