如何使用TypeScript键入类似jQuery的初始化模式?

时间:2018-10-21 15:18:01

标签: javascript jquery typescript

我在库中使用类似jQuery的初始化模式(我添加了一些有关我希望这些函数接受并返回的类型的注释):

function JQueryConstructor ( selector /*: string | INSTANCE_OF_JQUERY*/ ) {
  if ( selector.__jquery ) return selector;
}
function jQuery ( selector/*: string | INSTANCE_OF_JQUERY*/ )/*: INSTANCE_OF_JQUERY*/ {
  return new JQueryConstructor ( selector );
}

const fn = jQuery.fn = jQuery.prototype = JQueryConstructor.prototype = {
  constructor: jQuery,
  __jquery: true
};

fn.foo = function () {};
jQuery ( '*' ).foo; // => Function
const $mocked = { __jquery: true, foo () {} };
jQuery ( $mocked ) === $mocked; // => true

它可以在JavaScript中使用,但是如何在TypeScript中编写它以便正确键入?

我有以下要求/问题:

  • 我不想编写单独的声明文件(jQuery就是这种情况),它必须由TypeScript生成
  • TypeScript编译器输出的JavaScript一旦缩小,其大小应该基本相同,大小对于我的库很重要
  • TypeScript应该是JS的超集,但看起来我可以从JavaScript中称为new Foo ()的函数返回任意值,而TypeScript则抱怨只能这样调用void函数
  • 我需要能够扩展原型并生成适当的声明文件

我找不到解决所有这些问题的方法,您能帮我吗?

2 个答案:

答案 0 :(得分:0)

要按顺序解决您的要求/问题:

  • 如果我们的代码Typescript"declaration": true选项兼容,我们可以让编译器执行此操作
  • 那里有一个非常小的示例,但是一旦正确设置了管道,JS TS生成的大小应该与JS差不多
  • Typescript是JS的超集,因为任何有效的JS在语法上都是正确的TS。 Typescript确实执行了额外的语义检查。一种这样的检查是不允许使用new调用任何函数或不允许未命中匹配的类型。尽管如果您遇到语义错误,仍然可以发出JS(并且默认情况下编译器会发出),但是建议您修复错误。
  • Typescript允许接口与接口,接口与类,类和名称空间之间的各种合并,所有这些合并都旨在帮助实现类型安全的可扩展性。您可以阅读更多here

我想要的解决方案是使用类而不是函数JQueryConstructor并使用类接口合并来实现可扩展性。结果将类似于:

class JQuery{
    __jquery!: boolean; // Just the declaration
    constructor(selector: string | JQuery)  {
        if(typeof selector !== 'string' && selector.__jquery) return selector;
    }
}
JQuery.prototype.__jquery = true;

function foo() { };
interface JQuery {
    foo: typeof foo
}
JQuery.prototype.foo = foo;



function jQuery(selector: string | JQuery): JQuery {
    return new JQuery(selector);
}


jQuery('*').foo; // => Function
const $mocked = { __jquery: true, foo() { } };
jQuery($mocked) === $mocked; // => true

代码的缩小版本(缩小here)与您的代码没有太大区别,您的版本为300bytes,我编译为es2015的版本为261字节,编译为es5的版本为253字节(即使我使用您较长的JQueryConstructor名称仍为297个字节)。在这么小的示例中比较大小可能完全相关,但似乎具有可比性。

根据上面的代码,将生成以下定义:

declare class JQuery {
    __jquery: boolean;
    constructor(selector: string | JQuery);
}
declare function foo(): void;
interface JQuery {
    foo: typeof foo;
}
declare function jQuery(selector: string | JQuery): JQuery;
declare const $mocked: {
    __jquery: boolean;
    foo(): void;
};

这将适用于任何使用者,并将允许他们使用相同的类接口合并技术来提供可扩展性。

答案 1 :(得分:0)

由于@ titian-cernicova-dragomir,我想出了以下解决方案,由于必须显式定义所有接口,所以它有些冗长,但可以使用:

interface jQuery {
  constructor: typeof jQueryConstructor,
  __jquery: boolean
}

function isJQuery ( x ): x is jQuery {
  return x && x['__jquery'];
}

function jQuery ( selector: string | jQuery ): jQuery {
  if ( isJQuery ( selector ) ) return selector;
  return new jQueryConstructor ( selector );
}

function jQueryConstructor ( selector: string ) {}

const fn = jQuery.fn = jQuery.prototype = jQueryConstructor.prototype = {
  constructor: jQueryConstructor,
  __jquery: true
} as jQuery;


interface jQuery {
  foo ();
}

fn.foo = function () {};
jQuery ( '*' ).foo; // => Function
const $mocked = { __jquery: true, foo () {} } as jQuery;
jQuery ( $mocked ) === $mocked; // => true
jQuery ( '*' ) instanceof jQuery; // => true