4

我已阅读并尝试在“(e:CustomEvent)=> void”类型的参数中找到的建议不可分配给“EventListenerOrEventListenerObject”类型的参数,但无法将它们明智地应用于我的情况

我已经接手了一个 TS2.0 的项目,我希望用它迁移到 3.1。我已经阅读了有关标题中的错误并理解它发生的原因,但我的打字稿知识在这一点上是有限的

我有这些课程:

EventTargetImpl 似乎是一个用于管理事件侦听器集合的类

class EventTargetImpl implements EventTarget {
    private eventListeners: any = {};

    public addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void {
        if (!this.eventListeners.hasOwnProperty(type))
            this.eventListeners[type] = [];
        this.eventListeners[type].push(listener);
    }

    public dispatchEvent(event: Event): boolean {
        var type = event.type;
        if (!this.eventListeners.hasOwnProperty(type)) {
            this.eventListeners[type] = [];
        }
        for (var i = 0; i < this.eventListeners[type].length; i++) {
            this.eventListeners[type][i].call(this, event);
        }
        return true;
    }

    public removeEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void {
        if (!this.eventListeners.hasOwnProperty(type))
            return;
        var listenerIndex = this.eventListeners[type].indexOf(listener);
        if (listenerIndex === -1)
            return;
        this.eventListeners[type].splice(listenerIndex, 1);
    }
}

InputEvent 类定义了事件名称的常量字符串,它们是某种本地版本的事件。

class InputEvent extends EventImpl
{
    public static TYPE_UP = "up";
    constructor(type: string) {
        super(type);
    }
}

..但除此之外,除了扩展 EventImpl 之外似乎没有做太多事情:

class EventImpl implements Event
{
    public bubbles: boolean;
    public cancelBubble: boolean;
    public cancelable: boolean;
    public currentTarget: EventTarget;
    public defaultPrevented: boolean;
    public eventPhase: number;
    public isTrusted: boolean;
    public returnValue: boolean;
    public srcElement: Element;
    public target: EventTarget;
    public timeStamp: number;
    public type: string;
    public initEvent(eventTypeArg: string, canBubbleArg: boolean, cancelableArg: boolean): void { throw "not implemented";}
    public preventDefault(): void {
        this.defaultPrevented = true;
    }
    public stopImmediatePropagation(): void { throw "not implemented"; }
    public stopPropagation(): void { throw "not implemented";}
    public AT_TARGET: number;
    public BUBBLING_PHASE: number;
    public CAPTURING_PHASE: number;
    constructor(type: string) {
        this.type = type;
    }
}

输入包装器。这个类似乎拥有/持有一个 HTML 元素,并且要么为元素的事件注册标准处理程序,要么将它们替换为附加到自身的处理程序,在 InputEvent 中命名的本地类型事件

class InputWrapper extends EventTargetImpl {

    constructor(private element: HTMLElement) {
        super();
        this.attachListeners();
    }

    private attachListeners(): void {
        var detachGlobalListeners: () => void;
        var attachGlobalListeners: () => void;

        var mouseUpEventHandler = (event) => {
            var point = this.normaliseMouseEventCoords(event);
            this.handleMouseUp(point.x, point.y, event, InputType.Mouse);
            detachGlobalListeners();
        };

        this.element.addEventListener("mouseup", (event) => {
            if (!this.globalMode) {
                this.handleMouseUp(event.offsetX, event.offsetY, event, InputType.Mouse);
            }
        });

        detachGlobalListeners = () => {
            this.globalMode = false;
            window.removeEventListener("mouseup", mouseUpEventHandler);
        };

        attachGlobalListeners = () => {
            this.globalMode = true;
            window.addEventListener("mouseup", mouseUpEventHandler);
        };
    }

    private handleMouseUp(x: number, y: number, event: Event, inputType: InputType): void {
        event.stopPropagation();
        event.preventDefault();
        this.dispatchEvent(new InputEvent(InputEvent.TYPE_UP, { x: x, y: y }, inputType));
    }
}

这是使用它的地方,也是我得到错误的地方。我在上面的代码行中的 dispatchEvent 调用上也遇到了类似的错误:

        var inputWrapper = new InputWrapper(this.foreground);
        inputWrapper.addEventListener(InputEvent.TYPE_UP, (event: InputEvent) => { 
          this.handleMouseUp(event.coords.x, event.coords.y); 
        });

我链接了一个我已经看过的SO答案;我喜欢 jcalz 的建议,即我可以在本地事件中声明合并为 InputEvent 类型,然后使用 addEventHandler 的强类型重载,而不是字符串类型的重载,但我不知道在哪里/如何在其中执行此操作我拥有的对象/继承层次结构

我还尝试了 FrederikKrautwald 的建议,即as (e: Event) => void)添加到内联函数的末尾,但 TypeScript 更改为抱怨

将类型 '(event: InputEvent) => void' 转换为类型 '(e: Event) => void' 可能是一个错误,因为这两种类型都没有与另一种充分重叠

JacobEvelyn 的建议的修改形式最像我在 C# 中做的那样,如果我有一个特定类型装箱在一个通用类型中:

inputWrapper.addEventListener(InputEvent.TYPE_UP, (event: Event) => {
   let iv: InputEvent = (event as InputEvent); 
   this.handleMouseUp(iv.coords.x, iv.coords.y); 
});

但我再次得到“两种类型都没有与另一种充分重叠”错误

4

1 回答 1

1

如何解决“两种类型都没有充分重叠另一种”错误

初步警告:一般来说,类型断言不是类型安全的。编译器通常不允许你做事,因为你做这些事是错误的。但是,也有许多情况下,您确定某事是安全的,但编译器不相信。而且您可以使用类型断言来强制编译器接受您的事实版本。这一切都很好,除非你碰巧错了,在这种情况下,你可能会期待疯狂的运行时行为,你不应该责怪试图警告你的糟糕的编译器。但是,编译器不像人类那么聪明,所以断言通常是必要的。从这里开始,我假设您要做出的断言足够安全。

这是一个给出您遇到的错误的一般示例。

function assert<T, U>(t: T): U {
  return t as U; // error
  // [ts] Conversion of type 'T' to type 'U' may be a mistake because 
  // neither type sufficiently overlaps with the other. If this was 
  // intentional, convert the expression to 'unknown' first.
}

我们试图断言 type 的值T是 type U,但我们得到了“两种类型都没有充分重叠”的错误。基本上类型断言可用于将类型扩展为超类型,这始终是安全的,因为您更通用(每只狗都可以正确地称为动物);并且它们还可以用于将类型缩小到更具体的类型,这是不安全的,因为您更具体(并非每只动物都可以正确地称为狗)......这种缺乏类型安全性是故意的并且我们首先有断言的原因。但是在 TypeScript 中,类型断言不能用于直接从一种类型转换为不相关的类型既不窄也不宽的类型(所以我可以直接断言猫是动物,我可以直接断言动物是狗,但我不能直接断言猫是狗)。

错误消息中也提到了解决此问题的关键:“将表达式转换为unknown第一个”:

function assert<T, U>(t: T): U {
  return t as unknown as U; // works
}

错误已经消失,因为我们已经t从 type一直扩大Ttop type unknown,然后从unknowndown 缩小到 type U。这些步骤中的每一个都是允许的,但您不能跳过中间步骤。(我不能说“这只猫是一只狗”,但我可以说“这只猫是一只动物,它是一只狗”。)

您可以使用其他可能的中间类型;它必须是两个不相关类型的公共超类型或公共子类型:

  return t as unknown as U; // okay, widen T to top type unknown, and narrow unknown to U
  return t as (T | U) as U; // okay, widen T to (T | U), and narrow (T | U) to U

  return t as never as U; // okay, narrow T to bottom type never, and widen never to U
  return t as (T & U) as U; // okay, narrow T to (T & U), and widen (T & U) to U

  return t as any as U; // okay, any is both top-like and bottom-like, 
  // so you are, uh, "narrowidening" it through any 

这种中间人断言是一种相当普遍的做法(尤其是t as any as U,因为unknown它只在 TypeScript 3.0 中引入),所以不要对使用它感到太难过。繁琐的本质主要是迫使你思考你在做什么:你确定这个值可以被当作这种类型吗?如果没有,其他一些解决方案会比类型断言更好。如果是这样,那就继续吧!

希望有帮助;祝你好运!

于 2018-12-18T17:19:45.887 回答