简体   繁体   English

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

[英]How do I type a jQuery-like initialization pattern with TypeScript?

I'm using a jQuery-like initialization pattern in my library (I've added a few comments about the types I want these functions to accept and return): 我在我的库中使用类似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

it works in JavaScript, but how do I write it in TypeScript so that it's properly typed? 它可以在JavaScript中使用,但是如何在TypeScript中编写它以便正确键入?

I have these requirements/problems: 我有以下要求/问题:

  • I don't want to write a separate declaration file (this is the case for jQuery), it must be generated by TypeScript 我不想编写单独的声明文件(jQuery就是这种情况),它必须由TypeScript生成
  • The outputted JavaScript by the TypeScript compiler should have basically the same size once minified, size is important for my library TypeScript编译器输出的JavaScript缩小后应该具有基本相同的大小,大小对于我的库很重要
  • TypeScript is supposedly a superset of JS, but it looks like I can return arbitrary values from functions called as new Foo () in JavaScript, while TypeScript is complaining that only void functions can be called like that TypeScript应该是JS的超集,但看起来我可以从JavaScript中称为new Foo ()函数返回任意值,而TypeScript则抱怨只能像这样调用void函数
  • I need to be able to extend the prototype and have a proper declaration file generated 我需要能够扩展原型并生成适当的声明文件

I couldn't find a way to solve all these problems, can you help me? 我找不到解决所有这些问题的方法,您能帮我吗?

To address your requirements/problems in order: 依次解决您的要求/问题:

  • We can get the compiler to do this if our code is Typescript compliant with the "declaration": true option 如果我们的代码符合Typescript"declaration": true选项,则可以让编译器执行此操作
  • You have a pretty small example there, but once you get the plumbing right the JS TS generates should be about the same size as the JS 您在这里有一个非常小的示例,但是一旦获得正确的管道,JS TS生成的大小应该与JS差不多。
  • Typescript is a superset of JS in that any valid JS is syntactically correct TS. Typescript是JS的超集,因为任何有效的JS在语法上都是正确的TS。 Typescript does perform extra semantic checks. Typescript确实执行了额外的语义检查。 One such check is to not allow any function to be called with new or to not allow miss matched types. 这样的检查之一是不允许使用new函数调用任何函数或不允许未命中匹配的类型。 Although you can still emit JS if you have semantic errors (and by default the compiler will do so) it's recommended you fix the errors. 尽管如果您遇到语义错误,仍然可以发出JS(默认情况下编译器会发出),但是建议您修复错误。
  • Typescript allows all sorts of merging between interfaces and interfaces, interfaces and classes, classes and namespaces all of which are meant to help with type-safe extensibility. Typescript允许在接口和接口,接口和类,类和名称空间之间进行各种合并,所有这些合并都旨在帮助实现类型安全的可扩展性。 You can read more here 你可以在这里阅读更多

My solution to what you want would be to use a class instead of the function JQueryConstructor and use class-interface merging for extensibility. 我想要的解决方案是使用类而不是函数JQueryConstructor并使用类接口合并来实现可扩展性。 The result would look something like: 结果如下所示:

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

The minified versions of the code (minified here ) don't differ greatly from yours, Your version is 300bytes, my version compiled to es2015 is 261 bytes and compiled to es5 is 253 bytes (even if I use your longer JQueryConstructor name it's still 297 bytes). 代码的最小版本( 在此处最小)与您的代码没有太大不同,您的版本为300bytes,我编译为es2015的版本为261字节,编译为es5的版本为253字节(即使我使用更长的JQueryConstructor名称,它仍为297字节)。 Comparing sizes on such a small example might to be all taht relevant but they seem to be comparable. 在这么小的示例中比较大小可能完全相关,但似乎是可比较的。

Based on the above code the following definition is generated: 根据上面的代码,将生成以下定义:

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;
};

Which will work for any consumers and will allow them to use the same class-interface merging technique to provide extensibility. 它将对任何使用者都有效,并允许他们使用相同的类接口合并技术来提供可扩展性。

Thanks to @titian-cernicova-dragomir I've come up with the following solution, it's somewhat verbose due to having to explicitly define all the interface, but it works: 感谢@ 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM