【问题标题】:Discriminating between callback union types with Typescript使用 Typescript 区分回调联合类型
【发布时间】:2021-09-03 22:08:38
【问题描述】:

我正在将一些代码转换为 TS,并希望实现这个接口

let handler = new Handler()
  .on("data", (content) => /*something*/)
  .on("finished", (content) => /*something else*/);

但我遇到了区分回调类型的问题。 将回调放入有区别的联合中不起作用,因为它们具有不同的签名。 这是我到目前为止所得到的:

interface Content {
    x: string,
    y: string,
}

type EventType = "data" | "error" | "finished";

type DataCallback = (content: string) => void;
type ErrorCallback = (content: string) => void;
type FinishedCallback = (content: Content) => void;

type EventCallback = DataCallback | ErrorCallback | FinishedCallback;

class Handler {
    ondata: DataCallback;
    onerror: ErrorCallback;
    onfinished: FinishedCallback;

    on(event: EventType, callback: EventCallback) {
        if (event === "data") this.ondata = callback;
        else if (event === "error") this.onerror = callback;
        else if (event === "finished") this.onfinished = callback;
        else console.error(`Unhandled event: ${event}`);

        return this;
    }
}

但这会产生以下错误:

      TS2322: Type 'EventCallback' is not assignable to type 'DataCallback'.
  Type 'FinishedCallback' is not assignable to type 'DataCallback'.
    Types of parameters 'content' and 'content' are incompatible.
      Type 'string' is not assignable to type 'Content'.

这完全有道理。

这个接口在 Typescript 中是不可能实现的吗?

一种选择是将界面更改为

onData(callback: DataCallback) { this.ondata = callback; }

等等,但我希望我可以保持相同的界面。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以使用与window.addEventListener 相同的模式。

    您首先将您的事件类型映射到他们期望的回调类型:

    interface EventCallbacks {
        "data": DataCallback,
        "error": ErrorCallback,
        "content": ContentCallback,
    }
    

    然后你输入你的.on方法

    on<E extends keyof EventMap>(event: E, callback: EventCallbacks[E]): Handler;
    

    现在在编写on("data", ...) 时打字稿会知道第二个参数必须是DataCallback

    【讨论】:

    • 谢谢!这有帮助,但是在分配 this.ondata = callback 时,我仍然会遇到同样的错误。
    • this.ondata = callback as DataCallback 一起使用。这样做有什么缺点吗?
    • @Anton 我认为as 断言在这里应该是安全的,是的,但我当然可能忽略了一些东西!做出断言会绕过 typescript 的类型系统,因此如果你做出断言,TS 将不再保证安全。当然 TS 是有限的(这一切似乎都与stackoverflow.com/a/65831204/11308378 有关),有时它是唯一明智的选择。
    • 我在之前的评论中链接的答案实际上用Math.random 很好地说明了这种情况:如果你以on(Math.random() &lt; 0.5 ? "data" : "content", ...) 开头,typescript 的第二个参数应该是什么。它无法在编译时确定回调类型,因此on的实现实际上也无法知道真正的callback类型
    • 原来on&lt;E extends keyof EventCallbacks&gt;(event: E, callback: EventCallbacks[E]) { this._events[event] = callback; } 在没有断言的情况下工作。
    猜你喜欢
    • 2020-05-23
    • 1970-01-01
    • 1970-01-01
    • 2018-11-04
    • 1970-01-01
    • 1970-01-01
    • 2020-07-30
    • 2020-05-07
    • 1970-01-01
    相关资源
    最近更新 更多