Module vs Namespace - Import vs Require Typescript

时间:2016-07-26 06:20:49

标签: typescript

我对module/namespace/exportimport, require, reference的使用感到很困惑。 来自Java背景,有人可以简单地解释我何时使用什么和什么是正确的设计?我在编写示例项目时觉得我搞砸了

到目前为止,这是我的理解 1. module用于外部包裹 2. namespace用于内部包

  • 我没知道我们如何对它们进行分类?
  • 何时导出类或命名空间或包?
  • 如果我们导出包/名称空间,则导出其中的所有类或需要显式导出
  • 如何导入/需要每个人?

According to doc,如果我为每个经理/模型创建每个“ts”文件,Typescript不建议使用“命名空间”?直接使用参考路径?

请详细说明,因为我来自不同的背景,不确定ES6 / ES5等。

我看到有几个人对同样的问题感到困惑。我希望有人可以用现实世界的场景详细解释

5 个答案:

答案 0 :(得分:57)

我没有得到我们如何对它们进行分类?

命名空间用于组织/封装您的代码。外部模块用于组织/封装代码并在运行时定位代码。在实践中,您在运行时有两个选择:1)将所有已转换的代码组合到一个文件中,或者2)使用外部模块并拥有多个文件,并需要一些其他机制来获取这些文件。

何时导出类或命名空间或包?

要使类型或值在其所在文件之外可见,您必须将其导出(如果它在命名空间内)。无论是在顶层还是在命名空间中导出它,都将决定它现在是否在外部模块中。

如果我们导出包/名称空间,则导出其中的所有类或需要显式导出

命名空间中的类总是需要显式导出,以使类在编译时在编译它的文件之外可见。

如何导入/需要每个人?

这取决于您是否使用外部模块。始终需要将外部模块导入"使用"它。导入不在外部模块中的命名空间实际上只是为命名空间提供别名 - 你仍然必须在类型/任何别名前加上别名(这就是你通常不想要的原因)将命名空间与外部模块一起使用;这样做意味着在引用外部模块提供的任何内容时总是必须使用前缀。)外部模块中不存在的命名空间可以跨越文件,因此如果你在相同的命名空间,您可以引用命名空间导出的任何内容,而无需任何类型的导入。

要真正理解上述内容,您需要一些背景知识。使用references / namespaces / external modules理解的关键是这些构造在编译时的作用以及它们在运行时的作用。

在编译时使用引用指令来定位类型信息。您的来源中有一个特定的符号。 TypeScript编译器如何定位该符号的定义?参考指令主要由tsconfig.json机制包含 - 使用tsconfig.json,您告诉编译器所有源都在哪里。

命名空间可以包含类型定义和/或实现。如果命名空间仅包含类型信息,那么它根本没有运行时表现形式 - 您可以通过查看JS输出并查找空JS文件来检查它。如果命名空间具有实现代码,则代码将封装在一个闭包内,该闭包分配给与名称空间同名的全局变量。使用嵌套的命名空间,根名称空间将有一个全局变量。再次,检查JS输出。历史上,命名空间是JS客户端库如何尝试避免命名冲突的问题。我们的想法是将整个库封装到一个闭包中,然后尽可能小地展示全局 - 只需一个引用闭包的全局变量。嗯,问题仍然是你在全球空间声称了一个名字。如果您想要两个版本的库,该怎么办? TypeScript命名空间仍然存在如何定位命名空间源的问题。也就是说,引用A.B的源代码仍然存在告诉编译器如何定位A.B的问题 - 通过使用引用指令或使用tsconfig.json。或者将命名空间放入外部模块,然后导入外部模块。

外部模块源自服务器端JS。外部模块与文件系统上的文件之间存在一对一的对应关系。您可以使用文件系统目录结构将外部模块组织为嵌套结构。导入外部模块通常会在该外部模块上引入运行时依赖性(例外情况是当您导入外部模块但后来不在值位置使用其任何导出时 - 也就是说,您只导入外部模块获取其类型信息)。外部模块隐式位于闭包中,这是关键:模块的用户可以将闭包分配给他们想要的任何局部变量。 TypeScript / ES6增加了将外部模块的导出映射到本地名称的附加语法,但这只是一个非常好的方法。在服务器端,定位外部模块相对简单:只需在本地文件系统上找到代表外部模块的文件即可。如果要在客户端使用外部模块,在浏览器中,它会变得更加复杂,因为没有与可用于加载模块的文件系统等效。所以现在在客户端你需要一种方法将所有这些文件捆绑成一个可以在浏览器中远程使用的表单 - 这就是像Webpack这样的模块捆绑器(Webpack比捆绑模块更多)和Browserify发挥作用。模块捆绑器允许在浏览器中运行时解析外部模块。

真实场景:AngularJS。假设外部模块不存在,使用单个命名空间来限制全局空间的污染(在下面的示例中,单个变量MyApp就在全局空间中),仅导出接口,并使用AngularJS依赖注入使实现可用。将所有类放在目录根目录中,将tsconfig.json添加到根目录,在同一目录根目录下安装angularjs typings,以便tsconfig.json也将其选中,将所有输出结合到一个JS文件中。如果代码重用不是一个问题,这对大多数项目都可以正常工作。

MyService.ts:

namespace MyApp {

    // without an export the interface is not visible outside of MyService.ts
    export interface MyService { 
        ....
    }

    // class is not exported; AngularJS DI will wire up the implementation
    class MyServiceImpl implements MyService {
    }

    angular.module("MyApp").service("myService", MyServiceImpl);
}

MyController.ts:

namespace MyApp {

   class MyController {
       // No import of MyService is needed as we are spanning 
       // one namespace with multiple files.
       // MyService is only used at compile time for type checking. 
       // AngularJS DI is done on the name of the variable. 
       constructor(private myService: MyService) { 
       }
   }
   angular.module("MyApp").controller("myController", MyController);
}

使用IIFE避免污染全局运行时范围。在此示例中,根本不会创建全局变量。 (假设是tsconfig.json。)

Foo.ts:

namespace Foo {
    // without an export IFoo is not visible. No JS is generated here
    // as we are only defining a type.
    export interface IFoo {
        x: string;
    }
}

interface ITopLevel {
    z: string;
}

(function(){
    // export required above to make IFoo visible as we are not in the Foo namespace
    class Foo1 implements Foo.IFoo {
        x: string = "abc";
    }
    // do something with Foo1 like register it with a DI system
})();

Bar.ts:

// alias import; no external module created
import IFoo = Foo.IFoo;

(function() {

    // Namespace Foo is always visible as it was defined at
    // top level (outside of any other namespace).
    class Bar1 implements Foo.IFoo {
        x: string;
    }

    // equivalent to above
    class Bar2 implements IFoo {
        x: string;
    }

    // IToplevel is visible here for the same reason namespace Foo is visible
    class MyToplevel implements ITopLevel {
        z: string;
    }

})();

使用IIFE可以消除在第一个示例中将MyApp作为全局变量的引入。

MyService.ts:

interface MyService { 
    ....
}

(function() {

    class MyServiceImpl implements MyService {
    }

    angular.module("MyApp").service("myService", MyServiceImpl);
})();

MyController.ts:

(function() { 

   class MyController { 
       constructor(private myService: MyService) { 
       }
   }

   angular.module("MyApp").controller("myController", MyController);
})();

答案 1 :(得分:20)

有两件事:

  • TypeScript中的模块是标准的ES6概念,它在代码的顶级使用import / export个关键字; < / LI>
  • 名称空间是特定于TypeScript的概念,有助于以过时的方式组织代码。

命名空间

这几乎是一个过时的概念。在ES6模块之前,在浏览器中分离JavaScript代码的常用方法是创建全局变量。例如,像下划线这样的API的所有功能都位于名为_的全局变量中。

这种旧方法可以像Java包或PHP命名空间一样。它不适应Web。新的ECMAScript标准解决了以下问题:如何使用两个具有相同名称的库?如何使用同一个库的两个不同版本?

注意:在ECMAScript定义“modules”(2014年夏季)之前的TypeScript版本中,名称空间称为“内部模块”,模块称为“外部模块”。

注意2:export中的关键字namespace是关键字的非标准TypeScript用法。这是声明可以从命名空间外部公开访问的东西的方法。

ES6模块

模块是一个文件,在代码的顶层包含关键字importexport

TypeScript遵循ECMAScript的标准。我建议在Mozilla的一篇文章中阅读a good introduction to ES6 modules

如果要在前端应用程序中使用模块(在浏览器中),则必须使用捆绑器(Webpack [the documentation here],Browserify)或加载器({ {3}} [SystemJS],RequireJS)并使用此环境配置TypeScript。

如果您的代码在Node.js中执行,只需配置TypeScript编译器即可生成CommonJS格式。

注意:名称空间可以在模块中声明。在这种情况下,它不能从模块外部作为全局变量访问。但是,它可以从模块中导出。

答案 2 :(得分:12)

  
      
  1. 模块用于外部包2.命名空间用于内部包
  2.   

实际上,module关键字已替换为namespace关键字。

因此,更好的声明 Modules 以前称为外部模块, namespace 以前称为内部模块。

更多

希望这有助于进一步:https://basarat.gitbooks.io/typescript/content/docs/project/modules.html

答案 3 :(得分:4)

需要”和“导入”在功能上是等效的。我们可以互换使用它们,因为我们的浏览器并不真正关心浏览器是否原生支持它们。 但是,虽然“要求”的根源在于从CommonJS回溯到2009年的旧编码风格,但“import”正在从广泛接受的ES6(ES2015)语法中推导出它的语法。 所以你应该对新项目使用“ import ”而不是“ require ”。

答案 4 :(得分:0)

命名混乱

在Typescript的早期,名称空间被称为内部模块,而ES6模块被称为外部模块

现在要声明名称空间,Typescript团队建议使用namespace { }而不是module { }语法,以避免与外部模块的命名混淆。因为现在外部模块只是“模块”,而内部模块是“命名空间”。


命名空间

声明

可以使用namespacemodule关键字声明Typescript中的名称空间。这两个关键字执行相同的操作。然后,我们可以使用export关键字来决定将名称空间的哪一部分公开。

// LivingThings.ts
export namespace Animals {
    export class Dog { }
    export class Cat { }
}
export namespace Plants {
    export class Orchid { }
    export class Bamboo { }
}

// LivingThingsUser.ts
import { Animals, Plants } from "./LivingThings"

逻辑分组

在ES6之前,Typescript中使用了命名空间来封装接口,类,函数和变量,以支持一组相关功能并隐藏实现细节。这样,我们可以防止变量泄漏到全局空间中。这有助于更好的代码组织并防止名称冲突。 现在,建议使用ES6模块来实现这一目标。

名称空间现在用于环境名称空间声明。

单个文件的使用

我们可以在多个文件中声明名称空间,可以使用--outFile标志将它们连接起来。然后,我们可以在HTML页面的<script>标记内使用该级联文件。这样一来,我们就可以在包含所有依赖项的客户端Web应用程序中很好地构建代码。


模块

声明

模块也称为ES6模块。我们使用多个文件对相关功能进行分组,仅使用export关键字即可公开显示所需对象。

// Animals.ts
export class Dog { }
export class Cat { }

// Plants.ts
export class Orchid { }
export class Bamboo { }

// LivingThingsUser.ts
import { Dog, Cat } from "./Animals"
import { Orchid, Bamboo } from "./Plants"

逻辑分组

模块中的逻辑分组是通过使用单独的文件对相关功能进行分组来实现的。因此,外部模块也称为文件模块

单个文件的使用

我们不使用<script>标记来加载客户端Web应用程序的模块,因为在下载这么多文件并同时渲染页面时,浏览器可能会变慢。为此,我们使用诸如CommonJS,AMD,SystemJS之类的模块加载器,这些模块加载器使我们能够异步加载文件或将外部模块文件连接到单个优化文件中。

对于服务器端,尤其是在Node.js中,强烈建议使用这些模块。

就是这样!