模块/命名空间的一般 TypeScript 用法

General TypeScript usage for modules / namespaces

我有一个使用 jQuery 和其他库的标准网页(不是使用 angular、react、vue 等呈现的应用程序)。

我想将好的做法与 TypeScript 相结合。 做这个的最好方式是什么?

我目前的想法是有3个文件:

  1. index.d.ts(描述我模块的类型)
  2. test.ts(执行index.d.ts中描述的类型)
  3. page.js(使用test.js中定义的javascript的文件——从test.ts输出)

我目前有这些内容:

index.d.ts

// Type definitions for test 1.0.0
// Project: Test
// Definitions by: Author Name

/// <reference path="../../lib/@types/jquery/index.d.ts" />
declare namespace TestNS {
    export class TestClass {
        // Fields
        element: JQuery;
        value: string;

        constructor(element: JQuery, val: string);

        OnCreate();

        static AttachToJQuery(jq: JQueryStatic): void;
    }

    interface JQuery {
        TestClass(element: JQuery, val?: string): TestNS.TestClass;
    }

    interface JQueryStatic {
        TestClass(element: JQuery, val?: string): TestNS.TestClass;
    }
}

Test.ts(第 2 次加载,在 jQuery 之后)

/// <reference path="../../lib/@types/jquery/index.d.ts" />
/// <reference path="./index.d.ts" />

export namespace TestNS {
    export class TestClass {
        // Fields
        element: JQuery;
        value: string;

        constructor(element: JQuery, val: string) {
            this.element = element;
            this.value = val;
            this.OnCreate();
        }

        OnCreate() {
            this.element.data('test-value', this.value);
        }

        static AttachToJQuery(jq: JQueryStatic) {
            //no jQuery around
            if (!jq) {
                return false;
            }

            jq.fn.extend({
                TestNS: function(newVal) {
                    return this.each(function() {
                        var obj = jq(this);
                        new TestNS.TestClass(obj, newVal);

                    });
                }
            });
        }//attach to jquery method (to easily work with noconflict mode)
    }//test class
}

page.js(最后加载)

let newJquery:JQueryStatic = jQuery.noConflict();
TestNS.TestClass.AttachToJQuery(newJquery);

let testElems = newJquery(".testClass");
testElems.TestClass();

我的目标是将我的代码整齐地组织到打字稿和页面上的命名空间中(但在打字稿中这样做会产生与重复标识符相关的错误)以及模块化和可扩展。我知道我应该在 "node_modules/@types" 目录下发布我的类型,但我现在只想将它们全部封装在这个模块中。

我试过使用一个模块,并导入 index.d.ts 中定义的内容,但是 TypeScript 说我无法导入这个文件,而且找不到模块名称。我确实了解到,如果我使用一个模块,它应该与一个加载器一起使用,例如 CommonJS、RequireJS 或 AMD,但我现在宁愿避免这种情况(如果这样做不是一种可怕的做法,就像我想的那样)暂时将复杂程度降至最低)。

我试过查看其他项目,但他们都使用加载器,这对这种脚本来说似乎有点过分了。

有什么好办法吗?

假设你只想在一个简单的项目中尝试 TypeScript,我建议你稍微简化一下:

  • 删除index.d.ts。定义文件描述了现有的 javascript 代码,因此 TypeScript 可以识别其中的类型。你的项目中没有这种情况,没有必要重复你已经在你的test.ts中的类型信息,你可以安全地删除这个文件。

  • 只需将您的 test.ts 构建为 test.js。确保您的 jquery .d.ts 位于应有的位置。

  • 去掉page.js,把test.ts里的所有代码都写出来测试一下。一旦你让它工作,你就可以重新组织它。

但是,如果您的目标是让代码模块化和可扩展,建议的方法是确实使用模块:

Starting with ECMAScript 2015, modules are native part of the language, and should be supported by all compliant engine implementations. Thus, for new projects modules would be the recommended code organization mechanism.

https://www.typescriptlang.org/docs/handbook/namespaces-and-modules.html

要在浏览器的生产环境中使用模块,您需要:

  • 捆绑器。一种将调用 TypeScript 来构建您的个人 TS 模块,然后将构建输出组合到单个 javascript 文件中的工具。然后,您将把这个文件包含到您的 HTML 中。 WebPack 可能是最容易上手的:https://webpack.js.org/concepts/

  • 包管理器。安装和管理第三方模块版本的工具。 https://www.npmjs.com/

这是一个很大的研究领域,但我不认为这是一个矫枉过正的问题。如果您的代码是模块化的(即使它只有 2 个模块),则您需要一个基础设施来以一种或另一种方式捆绑和加载您的模块。有多种选择具有不同的好处和权衡,但基本上没有办法解决这个问题。

如果你想让事情在没有模块加载器的情况下工作,你将不得不做一些改变。首先值得一提的是,TypeScript 支持 2 种主要的模块化代码、命名空间和模块的方法。

注意:这里有点令人困惑的细微差别是关键字 namespacemodule 可以互换使用,并且不能单独确定事物是模块还是命名空间。

1) 模块(以前称为外部模块)- 这些使用典型的导入、导出和需要语义,并且需要模块加载器,最好是某种捆绑器。对于此模式,每个文件都被视为一个模块。任何包含任何导入或导出语句的文件都将被 TypeScript 编译器视为外部模块。

2) 命名空间(以前称为内部模块)- 这种模式支持可以跨越多个文件的命名空间。如果您不想使用模块加载器,这是您需要使用的模块类型。这种模式的使用越来越少,但如果你出于任何原因想要使用它,它仍然是一个选择。要使用这种类型的模块化,文件中不能有任何导入或导出语句。

这是 namespaces https://www.typescriptlang.org/docs/handbook/namespaces.html

上的文档

假设您想按照最初的计划进行,则需要在代码中进行一些调整。

1) 如@sbat 所述,您的 index.d.ts 正在重新声明您的名称空间和 class 类型。这很可能导致重复定义错误。您可以通过简单地覆盖 JQuery 界面来替换全部内容。

/** Extend the JQuery interface with custom method. */
declare interface JQuery {
    TestNS: () => TestNS.TestClass
}

2) 确保您没有任何顶级导出或导入。具体来说,您需要从 test.ts 中的命名空间中删除 export。这将使您的模块成为 internal 而不是 external

namespace TestNS {
    export class TestClass {
        ...
    }
}