模块 vs 命名空间 - 导入 vs 需要打字稿 [英] Module vs Namespace - Import vs Require Typescript

查看:34
本文介绍了模块 vs 命名空间 - 导入 vs 需要打字稿的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 module/namespace/exportimport、require、reference 的用法感到很困惑.来自 Java 背景,有人可以简单地解释一下什么时候使用什么以及什么是正确的设计?写示例项目的时候感觉自己搞砸了

I am getting lot of confusion with module/namespace/export and import, require, reference usage. Being from Java background, Can someone explain me in nutshell when to use what and what's the right design? I feel I am messing up when I am writing sample project

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

So far this is my understanding 1. module is for external packages 2. namespace is for internal packages

  • 我不明白我们是如何对它们进行分类的?
  • 何时导出类、命名空间或包?
  • 如果我们导出包/命名空间,其中的所有类都被导出或需要显式导出
  • 如何导入/需要它们中的每一个?

根据 doc,如果我正在创建每个"ts"文件,Typescript 不建议使用命名空间"?直接使用引用路径?

According to doc, if I am creating each "ts" file for each manager/model, Typescript doesn't recommend using "namespaces"? Directly use reference paths?

请详细解释,因为我来自不同的背景并且不确定 ES6/ES5 等`.

Please explain in detail as I am coming from different background and not sure about ES6/ES5 etc`.

我看到有几个人提出/混淆了相同的问题.希望有人能详细解释一下真实世界的场景

I have seen several people raising/getting confused with same questions. I hope someone can explain in detail with real world scenario

推荐答案

我不明白我们是如何分类的?

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

Namespaces are used to organize/encapsulate your code. External modules are used to organize/encapsulate your code AND to locate your code at runtime. In practice, you have two choices at runtime: 1) combine all transpiled code into one file, or 2) use external modules and have multiple files and require some other mechanism to get at those files.

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

要使类型或值在其所在的文件之外可见,如果它在命名空间内,则必须将其导出.是在顶级还是在命名空间中导出它将决定它现在是否在外部模块中.

To make a type or value visible outside of the file that it's in, you have to export it if it's inside of a namespace. Whether you export it at the top level or within a namespace will decide if it's now in an external module.

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

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

Classes in a namespace will always need to be explicitly exported for the class to be visible at compile time outside of the file in which it is defined.

如何导入/需要它们中的每一个?

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

This depends of if you're using external modules. An external module will always need to be imported to "use" it. Importing a namespace that's not in an external module is really just providing an alias for the namespace -- you still have to prefix the type/whatever with the alias (and this is why you generally don't want to use namespaces with external modules; doing so means you always have to use a prefix when referencing anything provided by the external module.) Namespaces that aren't in an external module can span files, so if you're in the same namespace you can refer to anything exported by the namespace without needing any sort of import.

要真正理解上述内容,您需要一些背景知识.理解引用/命名空间/外部模块的关键是这些构造在编译时的作用以及它们在运行时的作用.

To really understand the above you need some background knowledge. The key thing to understand with references/namespaces/external modules is what these constructs do at compile time and what they do at runtime.

在编译时使用引用指令来定位类型信息.您的来源中有一个特定的符号.TypeScript 编译器如何定位该符号的定义?参考指令在很大程度上被 tsconfig.json 机制包含——使用 tsconfig.json,你告诉编译器你所有的源在哪里.

Reference directives are used at compile time to locate type information. Your source has a particular symbol in it. How does the TypeScript compiler locate the definition for that symbol? The reference directive has largely been subsumed by the tsconfig.json mechanism -- using tsconfig.json, you tell the compiler where all your sources are.

命名空间可以包含类型定义和/或实现.如果一个命名空间只包含类型信息,那么它根本没有运行时表现——你可以通过查看 JS 输出并找到一个空的 JS 文件来检查这一点.如果命名空间有实现代码,那么代码被包装在一个闭包中,该闭包被分配给一个与命名空间同名的全局变量.对于嵌套命名空间,根命名空间将有一个全局变量.再次检查 JS 输出.命名空间历来是 JS 客户端库试图避免命名冲突问题的方式.这个想法是将您的整个库包装到一个闭包中,然后公开尽可能小的全局足迹——只有一个引用闭包的全局变量.好吧,问题仍然是你在全局空间中声明了一个名字.如果你想要一个库的两个版本怎么办?TypeScript 命名空间仍然存在如何定位命名空间源的问题.也就是说,引用 A.B 的源代码仍然存在告诉编译器如何定位 A.B 的问题——通过使用引用指令或使用 tsconfig.json.或者将命名空间放入外部模块,然后导入外部模块.

Namespaces can contain type definitions and/or implementation. If a namespace contain only type information then it has no runtime manifestation at all -- you can check this by looking at the JS output and finding an empty JS file. If a namespace has implementation code, then the code is wrapped inside a closure that is assigned to a global variable with the same name as the namespace. With nested namespaces, there will be a global variable for the root name space. Again, check the JS output. Namespaces are historically how JS client-side libraries have attempted to avoid the issue with naming collisions. The idea is to wrap your entire library into one closure and then expose as small a global footprint as possible -- just one global variable referencing the closure. Well, the problem is still that you've claimed a name in the global space. What if you wanted, say, two versions of a library? A TypeScript namespace still has the issue of how to locate the source for the namespace. That is, source code that references A.B still has the problem of telling the compiler how to locate A.B -- either by using reference directives or by using tsconfig.json. Or by putting the namespace into an external module and then importing the external module.

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

External modules originated with server-side JS. There is a one-to-one correspondence between an external module and a file on the file system. You can use the file system directory structure to organize external modules into a nested structure. Importing an external module will generally aways introduce a runtime dependency on that external module (the exception is when you import an external module but then don't use any of its exports in the value position -- that is, you only import the external module to get at its type information). An external module is implicitly in a closure, and this is key: the user of the module can assign the closure to whatever local variable they want. TypeScript/ES6 adds additional syntax around mapping the exports of the external modules to local names, but this is just a nicety. On the server side, locating an external module is relatively straight forward: just locate the file representing the external module on the local file system. If you want to use external modules on the client side, in a browser, it gets more complex as there's no equivalent to the file system that has the module available for loading. So now on the client side you need a way to bundle all those files into a form that can be used remotely in the browser -- this is where module bundlers like Webpack (Webpack does a heck of a lot more than bundle modules though) and Browserify come into play. Module bundlers allow runtime resolution of your external modules in the browser.

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

Real world scenario: AngularJS. Pretend external modules don't exist, use a single namespace to limit pollution of global space (in the example below a single variable MyApp is all that is in the global space), export only interfaces, and use AngularJS dependency-injection to make implementations available for use. Put all classes in a directory root, add a tsconfig.json to the root, install angularjs typings under the same directory root so that tsconfig.json picks it up too, combine all output int one JS file. This will work fine for most projects if code-reuse isn't much of a concern.

MyService.ts:

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:

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.)

Using IIFE to avoid polluting global runtime scope. In this example, no global variables are created at all. (A tsconfig.json is assumed.)

脚:

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 作为全局变量引入.

Using IIFE you can make eliminate introducing MyApp as a global variable in the first example.

MyService.ts:

MyService.ts:

interface MyService { 
    ....
}

(function() {

    class MyServiceImpl implements MyService {
    }

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

MyController.ts:

MyController.ts:

(function() { 

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

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

这篇关于模块 vs 命名空间 - 导入 vs 需要打字稿的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆