简体   繁体   English

推断 typescript 中的映射参数类型

[英]Infer a mapped parameter type in typescript

I'm working with a library that has an interesting type declaration (simplified):我正在使用一个具有有趣类型声明(简化)的库:

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

And I'm trying to type the event in the callback that I provide:我正在尝试在我提供的回调中键入事件:

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

However "StreamDestroyedEvent" doesn't exist.但是“StreamDestroyedEvent”不存在。 It's not provided by the library, but rather exists only in that anonymous eventmapping, so I instead try to infer it:它不是由库提供的,而是仅存在于该匿名事件映射中,因此我尝试推断它:

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'>;

However, instead of giving me a type of {reason: string} it get the union type {reason: string;} | {id: number;}但是,它没有给我一个{reason: string}类型,而是得到联合类型{reason: string;} | {id: number;} {reason: string;} | {id: number;} . {reason: string;} | {id: number;} How can I get the right type, or is this as close as I'm going to get?我怎样才能得到正确的类型,或者这是否与我将得到的一样接近?

You'd like to basically "plug in" a type for the EventName type parameter.您基本上想为EventName类型参数“插入”一个类型。 While TypeScript supports this when calling a generic function, it doesn't do a good job representing such types in the type system itself.虽然 TypeScript 在调用泛型 function 时支持这一点,但它并不能很好地在类型系统本身中表示此类类型。 To do so would fully would require support for higher rank types , which aren't really part of the language.这样做完全需要支持更高级别的类型,这并不是语言的一部分。 And the emitter.on function by itself doesn't really let you call it "partially" so that you plug in EventName with the first parameter and then have the compiler tell you what the type of the second parameter should be.emitter.on function 本身并不能真正让您“部分地”调用它,以便您使用第一个参数插入EventName ,然后让编译器告诉第二个参数的类型应该是什么。

Before TypeScript 3.4 came out I'd probably say getting this information out of the compiler would be impossible.在 TypeScript 3.4 出来之前,我可能会说从编译器中获取这些信息是不可能的。 I'd try something like我会尝试类似的东西

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

and then be unable to get the StreamDestroyedEvent type to escape the function.然后无法获取StreamDestroyedEvent类型以逃避 function。

TypeScript 3.4 introduced improved support higher order type inference from generic functions . TypeScript 3.4 引入了改进的泛型函数支持高阶类型推断 You still can't represent what you're doing purely in the type system, but now we can define a function that gives us something we can that does the "partial" calling and retains the type information we need.你仍然不能纯粹在类型系统中表示你在做什么,但现在我们可以定义一个 function ,它为我们提供了一些我们可以做的“部分”调用并保留我们需要的类型信息。

Here is a currying function which takes a multi-argument function and returns a new function of one argument that returns another function of the remaining arguments: Here is a currying function which takes a multi-argument function and returns a new function of one argument that returns another function of the remaining arguments:

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

If you call curry on emitter.on , and then call that with "streamDestroyed" , (in TS3.4+) you get a function which takes a callback which takes a StreamDestroyedEvent , which you can now capture:如果您在emitter.on上调用curry ,然后使用"streamDestroyed"调用,(在 TS3.4+ 中)您会得到一个 function ,它需要一个回调,该回调需要一个StreamDestroyedEvent ,您现在可以捕获:

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

Note that the actual runtime behavior of the above isn't the point;请注意,上面的实际运行时行为不是重点; I tried to make sure it would actually do something reasonable, but you could also have just used type assertions to lie to the compiler about what was happening, as long as you don't get a runtime error when you run it:我试图确保它实际上会做一些合理的事情,但你也可以只使用类型断言来向编译器谎报正在发生的事情,只要你在运行它时没有遇到运行时错误:

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

Okay, hope that helps;好的,希望有帮助; good luck!祝你好运!

Link to code 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM