在 TypeScript 中扩展 Kendo UI Window

Extending Kendo UI Window in TypeScript

我正在尝试扩展 Telerik 的 Kendo UI 类 之一 Window 小部件。我正在使用 TypeScript 2.2.2.

供您参考,这是 Window 的定义。我只包括与这个问题相关的部分。

class Window extends kendo.ui.Widget {
    static extend(proto: Object): Window;
    constructor(element: Element, options?: WindowOptions);
    content(): string;
    content(content?: string): kendo.ui.Window;
    content(content?: JQuery): kendo.ui.Window;
}

我想覆盖 content(string) 方法。我使用 this example 作为我的代码的基础。所以我有以下内容:

export class AngularizedWindow extends kendo.ui.Window {
    constructor(element: Element, options?: kendo.ui.WindowOptions) {
        super(element, options);
    }

    content(content?: string) : kendo.ui.Window {
        console.log("Setting content");
        return super.content(content);
    }
}

但是,它给了我这个错误:

ts\widgets\AngularizedWindow.ts(2,18): error TS2415: Class 'AngularizedWindow' incorrectly extends base class 'Window'.
Types of property 'content' are incompatible.
Type '(content?: string) => Window' is not assignable to type '{ (): string; (content?: string): Window; (content?: JQuery): Window; }'.
Type 'Window' is not assignable to type 'string'.

我做错了什么?我不明白如何解释这个错误。

错误消息中的 "not assignable to" 类型,如果格式正确,是

{ 
   (): string; 
   (content?: string): Window; 
   (content?: JQuery): Window; 
}

这是一个有 3 个所谓 callable signatures 的类型,它描述了可以用 3 种方式之一调用的东西:

  • 没有参数,return正在处理一个字符串
  • 带有可选的字符串参数,returning Window
  • 带有可选的 JQuery 参数,returning Window

这就是 typescript 表示函数重载的方式 - 它是 kendo Window 中声明的 content 的实际类型,因为它有 3 个重载变体:

content(): string;
content(content?: string): kendo.ui.Window;
content(content?: JQuery): kendo.ui.Window;

Javascript 没有函数重载,所以打字稿会尽可能地模拟它,当你使用重载方法时它会起作用。

然而,当你在实现(或覆盖)重载方法时,打字稿没有帮助。您只能有一个实现,并且它必须在 运行 时间处理所有可能的参数组合。因此,您的扩展 class 必须重复 content() 的所有重载声明并提供一个实现,与所有声明的变体兼容并能够处理所有变体,如此处示例中所述:https://www.typescriptlang.org/docs/handbook/functions.html#overloads

我没有使用 Kendo UI 的经验,所以我只是根据使用 typescript 2.3 和 运行s in [=67] 编译的相关代码编写了最小示例=]:

base.ts

export class Widget {}
export class Element {}
export class WindowOptions {}
export class JQuery {}

export namespace kendo {
    export namespace ui {

        export class Window extends Widget {
            static extend(proto: Object): Window {return null}
            constructor(element: Element, options?: WindowOptions) { super() }
            content(): string;
            content(content?: string): Window;
            content(content?: JQuery): Window;
            content(content?: string | JQuery): Window | string {
                return null;
            }
        }

    }
}

d.ts

import { WindowOptions, JQuery, kendo } from './base';

export class AngularizedWindow extends kendo.ui.Window {
    constructor(element: Element, options?: WindowOptions) {
        super(element, options);
    }

    content(): string;
    content(content?: string): kendo.ui.Window;
    content(content?: JQuery): kendo.ui.Window;

    content(content?: string | JQuery) : kendo.ui.Window | string {
        if (typeof content === 'undefined') {
            console.log('implementation 1');
            return super.content();

        } if (typeof content === 'string') {
            console.log('implementation 2');
            return super.content(content);


        } else { // ought to be jQuery
            console.log('implementation 3');
            return super.content(content);
        }
    }
}

let a = new AngularizedWindow(null);

a.content();
a.content('b');
a.content({});

编译和运行

./node_modules/.bin/tsc base.ts d.ts
 node d.js

它打印

implementation 1
implementation 2
implementation 3

现在,当您查看示例代码时,它会引出一个问题:content() 真的需要所有这些重载声明吗?看起来采用联合类型和 returns 联合类型的实现足以处理所有用例。

但是,如果没有重载,此代码无法编译:

let s: string = a.content();

错误:

d.ts(32,5): error TS2322: Type 'string | Window' is not assignable to type 'string'.
  Type 'Window' is not assignable to type 'string'.

因此,重载允许描述参数类型和 return 类型之间的关系。但是,编译器在实现中并未强制执行这种关系。正如 this comment by one of typescript developers:

中所表达的那样,重载引入的额外复杂性是否值得值得商榷

Realistically, JavaScript does not have function overloading and in general I advise people to just not use overloading at all. Go ahead and use union types on parameters, but if you have multiple distinct behavioral entry points, or return types that differ based on inputs, use two different functions! It ends up being clearer for callers and easier for you to write.