打字稿索引类型组成

Typescript index type composition

我正在尝试这样输入我的事件 API:

type Apple = {
    seeds: number;
}
type Peach = {
    weight: number;
}

interface AddAppleEvent {
    apple: Apple;
}

interface AddPeachEvent {
    peach: Peach;
}

interface EventMap {
    "addApple": AddAppleEvent;
    "addPeach": AddPeachEvent;
}


class EventApi{
    on<K extends keyof EventMap>(type: K, listener: (ev: EventMap[K]) => any): void{    
    }
}

const api = new EventApi();
api.on("addApple", (evt) => {
    console.log(evt.apple);
});

这很奇怪:Typescript 从我注册的事件名称中知道我的 evt 变量中有什么。

但是:我希望能够注册到一个事件数组,然后键入结果(这将是一个每个事件类型的组成):

api.on(["addApple", "addPeach"], ({apple, peach}) => {
    if(apple) {
     // do something
    }
    if(peach) {
     // do something else
    }

})

不幸的是,我找不到一种方法来从接口迭代键数组来构建所有引用值的联合。

有什么想法吗?

Here is a link 使用上面的示例进入 typescript playground

-----编辑----

只是总结我得到的很好的答案 post:

非常感谢大家:)

我首先定义 EventMapKey 只是为了避免重新输入 keyof EventMap:

type EventMapKey = keyof EventMap;

那要看你要并集还是交集了

联盟

您可以这样定义 onMultiple

onMultiple<K extends EventMapKey[], EventType extends EventMap[K[number]]>(type: K, listener: (ev: EventType) => any): void {
    console.log("on add multiple event listenner", listener);
}

Playground link - 我添加了第三种事件类型(只是为了确定),最后 onMultiple 的示例用法有效:

api.onMultiple(["addApple", "addPeach"], (evt) => {
    // Here, evt is AddAppleEvent | AddPeachEvent
});

当然,在您可以使用 evtapplepeach 属性之前,您必须执行类型保护以检查您正在处理的事件类型。为此,我可能会在事件类型中添加一个 type,这样您就可以使用它:

interface AddAppleEvent {
    type: "addApple";
    apple: Apple;
}

interface AddPeachEvent {
    type: "addPeach";
    peach: Peach;
}

api.onMultiple(["addApple", "addPeach"], (evt) => {
    if (evt.type === "addApple") {
        console.log(evt.apple.seeds);
    } else {
        console.log(evt.peach.weight);
    }
});

Playground link

但如果您愿意,您也可以从 "apple" in evt 开始工作而不添加 type

api.onMultiple(["addApple", "addPeach"], (evt) => {
    if ("apple" in evt) {
        console.log(evt.apple.seeds);
    } else {
        console.log(evt.peach.weight);
    }
});

Playground link

路口

如果你想要一个交叉路口,这里是解决方案 :

type ComposedEventListener<K extends EventMapKey[]> = {
    [I in keyof K]: (ev: EventMap[Extract<K[I], EventMapKey>]) => any
}[number] extends (ev: infer I) => any ? (ev: I) => any : never

class EventApi {

    on<K extends EventMapKey>(type: K, listener: (ev: EventMap[K]) => any): void {

        console.log("on add event listenner", listener);
    }

    // here, write K as a set of keys from EventMap allows to pass multiple keys,
    // but I cant find how to type the listener properlly.
    onMultiple<K extends EventMapKey[]>(type: readonly [...K], listener: ComposedEventListener<K>): void {
        console.log("on add multiple event listenner", listener);
    }

}

const api = new EventApi();
api.on("addApple", (evt) => {
    console.log(evt.apple);
});

api.onMultiple(["addApple", "addPeach"], (evt) => { });

Playground link