TS3.1-模块(导⼊导出)
总结
同步导出
- export { ZipCodeValidator as mainValidator }重命名导出
- export * from "./StringValidator";重新导出export { test } from "./Calculator";
- export default function (s: string) {...}普通默认导出,标记为默认导出的类和函数的名字是可以省略的。
同步导⼊
- import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";重命名导⼊
- import * as validator from "./ZipCodeValidator";整个模块导⼊
- import "./my-module.js";直接导⼊,获取导出对象,直接创建关联关系,通常⽤于导⼊内部进⾏了某系操作的模块
- import validate, { other } from "./StaticZipCodeValidator";普通导⼊
异步导⼊
- 可能是由于该⽂档⽐较旧,import(...)是 ES6 ⽤于⽀持异步导⼊的。在 TS 4.1.2 中⽀持这个写法
默认导⼊导出
- ⾮ ES6 的默认导⼊导出,export default语法并不能兼容 CommonJS 和 AMD 的exports。
- export = ZipCodeValidator;⾮ ES6 的默认导出,若使⽤export =导出⼀个模块,则必须使⽤ TypeScript 的特定语法import module = require("module")来导⼊此模块。
特点
- 含有export或import的都是外部模块,也就是 ES6 中的模块。
- 模块中导出的引⽤类数据,⼀旦在模块外被修改,模块内的也会被修改。即,导⼊只是导⼊了该对象的引⽤。
- 只引⼊模块中的类型,TS 打包时不会打包这些模块。应该是有配置项可以配置引⼊类型时要写⼊typ
eof关键字
- tsconfig 中的 include 声明的是 TS 在进⾏类型检查时需要包含的⽂件,即使这些⽂件没有真实的引⽤关系。
- 在 TS 中可以导⼊ .d.ts ⽂件,但是编译后如果没有对应的 JS ⽂件便会报模块丢失。可以在项⽬中的 .d.ts ⽂件导⼊这些外部声明,或者在 tsconfig 中配置 include 包含这些⽂件。
declare module⽤于外部模块类型的声明
- ⼀个包含导⼊导出的 .d.ts ⽂件可以被认为是⼀个模块的类型
- 即使在declare module已经写了实现,但依然只是类型的声明;
- ⽀持外部模块嵌⼊式扩展的声明扩展,例如class.prototype的嵌⼊式扩展,可以使⽤declare module扩展声明。
- 普通的模块扩展使⽤import引⼊后再导出,实现实体和类型的同时扩展。
- 模块名为字符串,遵循模块路径的解析规则。会⾃动注册为路径对应模块的类型,相同名称的模块⽀持声明合并。即可以扩展⼀个 .d.ts ⽂件。
- 可以包含export和export default,表⽰该模块的导出的属性的类型和默认导出的类型。
- declare module "hot-new-module";仅仅声明模块本⾝,模块内允许包含任意属性,这些属性的类型都是 any;
- declare module "*!text" {...}声明⼀个可匹配⼀类模块的类型,它能够为import fileContent from "./!text";提供类型
在 .d.ts ⽂件中export as namespace mathLib;把该⽂件内的所有 export 作为⼀个命名空间,声明在全局有个 mathLib 对象。同时声明了该 .d.ts 对应的模块拥有的导出对象的类型。
TS 中类型是通过搜集得到的,由 tsconfig 中的 include 标明,会搜集不具有导⼊导出的 .ts.d.ts。通过明⽂导⼊也会扩展类型搜集范围
- 在模块模式中当要声明全局变量时使⽤declare global,或者说它叫所有模块注⼊变量会更准确点。
- declare var d3:string在⽆导⼊导出⽂件中,声明⼀个全局变量 d3 的存在,并在他的类型是 string
- declare global下的interface Window⽤来扩展 window 对象
⼀个 ts 模块编译后应编译成两部分,⼀部分是由 JS ⽂件提供实现,⼀部分是由 .d.ts 提供的类型⽀持,供外部使⽤。
正⽂
原⽂地址
关于术语的⼀点说明: 请务必注意⼀点,TypeScript 1.5 ⾥术语名已经发⽣了变化。 “内部模块”现在称做 “命名空间”。 “外部模块” 现在则简称为“模块”,这是为了与⾥的术语保持⼀致,(也就是说module X {相当于现在推荐的写法namespace X {)。
import语句介绍
从 ECMAScript 2015 开始,JavaScript 引⼊了模块的概念。TypeScript 也沿⽤这个概念。
模块使⽤模块加载器去导⼊其它的模块。在运⾏时,模块加载器的作⽤是在执⾏此模块代码前去查并执⾏这个模块的所有依赖。⼤家最熟知的 JavaScript 模块加载器是服务于 Node.js 的和服务于 Web 应⽤的。
注释:AMD是模块规范,Require.js是这个规范的实现。
注释:这种全局可见需要配合<script>标签引⼊,不然只是类型上的全局可见,⽆法访问实现。
TypeScript 与 ECMAScript 2015 ⼀样,任何包含顶级import或者export的⽂件都被当成⼀个模块。相反地,如果⼀个⽂件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)。
导出
导出声明
注释:在 TS 中,类型和实现都可以作为模块的导⼊和导出使⽤
任何声明(⽐如变量,函数,类,类型别名或接⼝)都能够通过添加export关键字来导出。
Validation.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && st(s);
}
}
导出语句
导出语句很便利,因为我们可能需要对导出的部分重命名,所以上⾯的例⼦可以这样改写:
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && st(s);
}
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };
重新导出
我们经常会去扩展其它模块,并且只导出那个模块的部分内容。重新导出功能并不会在当前模块导⼊那个模块或定义⼀个新的局部变量。ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && parseInt(s).toString() === s;
}
}
// 导出原先的验证器但做了重命名
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
或者⼀个模块可以包裹多个模块,并把他们导出的内容联合在⼀起通过语法:export * from "module"。
AllValidators.ts
export * from "./StringValidator"; // exports interface StringValidator
export * from "./LettersOnlyValidator"; // exports class LettersOnlyValidator
export * from "./ZipCodeValidator"; // exports class ZipCodeValidator
导⼊
导⼊⼀个模块中的某个导出内容
可以对导⼊内容重命名
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
将整个模块导⼊到⼀个变量,并通过它来访问模块的导出部分
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
具有副作⽤的导⼊模块
尽管不推荐这么做,⼀些模块会设置⼀些全局状态供其它模块使⽤。这些模块可能没有任何的导出或⽤户根本就不关注它的导出。使⽤下⾯的⽅法来导⼊这类模块:注释:没有任何导出的模块即使通过import也不会把模块内的所有变量全局注册。和这句有冲突?
import "./my-module.js";
默认导出
类和函数声明可以直接被标记为默认导出。标记为默认导出的类和函数的名字是可以省略的。
StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/;
export default function (s: string) {
return s.length === 5 && st(s);
}
Test.ts
import validate from "./StaticZipCodeValidator";
let strings = ["Hello", "98052", "101"];
// Use function validate
strings.forEach(s => {
console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});
default导出也可以是⼀个值
OneTwoThree.ts
export default "123";
Log.ts
import num from "./OneTwoThree";
console.log(num); // "123"
export = 和 import = require()
CommonJS 和 AMD 的环境⾥都有⼀个exports变量,这个变量包含了⼀个模块的所有导出内容。
CommonJS 和 AMD 的exports都可以被赋值为⼀个对象, 这种情况下其作⽤就类似于 es6 语法⾥的默认导出,即export default语法了。虽然作⽤相似,但是export default语法并不能兼容 CommonJS 和 AMD 的exports。
为了⽀持 CommonJS 和 AMD 的exports, TypeScript 提供了export =语法。
export =语法定义⼀个模块的导出对象。这⾥的对象⼀词指的是类,接⼝,命名空间,函数或枚举。
若使⽤export =导出⼀个模块,则必须使⽤ TypeScript 的特定语法import module = require("module")来导⼊此模块。
ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && st(s);
}
}
export = ZipCodeValidator;
Test.ts
import zip = require("./ZipCodeValidator");
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validator = new zip();
// Show whether each string passed each validator
strings.forEach(s => {
console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`);
});
简单⽰例
注释:以下是⼀个编译 TS ⽂件为⽬标⽂件的⽰例
下⾯我们来整理⼀下前⾯的验证器实现,每个模块只有⼀个命名的导出。
为了编译,我们必需要在命令⾏上指定⼀个模块⽬标。对于 Node.js 来说,使⽤--module commonjs;对于 Require.js 来说,使⽤--module amd。⽐如:
tsc --module commonjs Test.ts
编译完成后,每个模块会⽣成⼀个单独的.js⽂件。好⽐使⽤了 reference 标签,编译器会根据import语句编译相应的⽂件。
可选的模块加载和其它⾼级加载场景
注释:在书写时只引⼊了该模块的类型,不会打包这个引⼊的模块。TS 可以根据需要在引⽤到时才按需引⼊,CommonJS 虽然是同步的但也有按需的同步引⼊
有时候,你只想在某种条件下才加载某个模块。在 TypeScript ⾥,使⽤下⾯的⽅式来实现它和其它的⾼级加载场景,我们可以直接调⽤模块加载器并且可以保证类型完全。
编译器会检测是否每个模块都会在⽣成的 JavaScript 中⽤到。如果⼀个模块标识符只在类型注解部分使⽤,并且完全没有在表达式中使⽤时,就不会⽣成require这个模块的代码。省略掉没有⽤到的引⽤对性能提升是很有益的,并同时提供了选择性加载模块的能⼒。
注释:import id = require("...")能够实现按需引⼊,下⽂应该是有误的,这个⽅法不是为了访问模块导出的类型。
注释:由于 import 是同步引⼊,为了把这部分打包成按需引⼊所以只允许使⽤ import 引⼊的类型,为了确保安全应该加添typeof关键字(有校验配置项可以设置为:如果引⼊只是使⽤类型的话 import 上必须加上 typeof)
注释:在 4.1.2 版本中⽀持import("...")这种 ES6 的异步加载语法
这种模式的核⼼是import id = require("...")语句可以让我们访问模块导出的类型。模块加载器会被动态调⽤(通过require),就像下⾯if代码块⾥那样。它利⽤了省略引⽤的优化,所以模块只在被需要时加载。为了让这个模块⼯作,⼀定要注意import定义的标识符只能在表⽰类型处使⽤(不能在会转换成 JavaScript 的地⽅)。
为了确保类型安全性,我们可以使⽤typeof关键字。typeof关键字,当在表⽰类型的地⽅使⽤时,会得出⼀个类型值,这⾥就表⽰模块的类型。
注释:下⾯应该是三种加载器打包后的结果
⽰例:Node.js ⾥的动态模块加载
declare function require(moduleName: string): any;
import { ZipCodeValidator as Zip } from "./ZipCodeValidator";
if (needZipValidation) {
let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
let validator = new ZipCodeValidator();
if (validator.isAcceptable("...")) { /* ... */ }
}
⽰例:require.js ⾥的动态模块加载
declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;
import * as Zip from "./ZipCodeValidator";
if (needZipValidation) {
require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
let validator = new ZipCodeValidator.ZipCodeValidator();
if (validator.isAcceptable("...")) { /* ... */ }
});
}
⽰例:System.js ⾥的动态模块加载
declare const System: any;
import { ZipCodeValidator as Zip } from "./ZipCodeValidator";
if (needZipValidation) {
System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => {
var x = new ZipCodeValidator();
if (x.isAcceptable("...")) { /* ... */ }
});
}
使⽤其它的JavaScript库
要想描述⾮ TypeScript 编写的类库的类型,我们需要声明类库所暴露出的 API。
我们叫它声明因为它不是 “外部程序” 的具体实现。它们通常是在.d.ts⽂件⾥定义的。如果你熟悉 C/C++,你可以把它们当做.h⽂件。让我们看⼀些例⼦。
外部模块
注释:module 后的名称在 TS 中进⾏了全局注⼊。
在 Node.js ⾥⼤部分⼯作是通过加载⼀个或多个模块实现的。我们可以使⽤顶级的export声明来为每个模块都定义⼀个.d.ts⽂件,但最好还是写在⼀个⼤的.d.ts⽂件⾥。我们使⽤与构造⼀个外部命名空间相似的⽅法,但是这⾥使⽤module关键字并且把名字⽤引号括起来,⽅便之后import。例如:
node.d.ts (simplified excerpt)
注释:这⾥不是⼀个全局注册,他同样需要引⽤才能使⽤ url,只是这种写法不会出现 url 的引⽤提醒,只会出现它包含的 Url 接⼝的。等同于定义了⼀个内部模块,这个模块不被导出,只有模块内的⽅法或属性被导出了。
注释:这⾥是⼀个 .d.ts ⽂件,这⾥的function parse不是标准类型写法,在 .ts 中会报缺乏实体定义。下⽂的let sep也相同,应该可以理解为在 .d.ts ⽂件中可以书写实体,但是不需要写实体的实现。
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
注释:下⽂的<reference>标签⽤于标明当前模块的依赖模块
现在我们可以/// <reference>node.d.ts并且使⽤import url = require("url");或import * as URL from "url"加载模块。
/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("");
外部模块简写
假如你不想在使⽤⼀个新模块之前花时间去编写声明,你可以采⽤声明的简写形式以便能够快速使⽤它。
declarations.d.ts
注释:当模块名是个字符串时,指明该模块是⽤来声明字符串匹配的模块的。后⽂中提到这个这个字符串可以使⽤通配符。
declare module "hot-new-module";
简写模块⾥所有导出的类型将是any。
import x, {y} from "hot-new-module";
x(y);
模块声明通配符
某些模块加载器如和⽀持导⼊⾮ JavaScript 内容。它们通常会使⽤⼀个前缀或后缀来表⽰特殊的加载语法。模块声明通配符可以⽤来表⽰这些情况。
注释:通配符可以实现对⼀个类型的模块进⾏统⼀的类型定义
蛛丝:模块内可以写 expor default 作为整个模块的导出名
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
现在你可以就导⼊匹配"*!text"或"json!*"的内容了。
import fileContent from "./!text";
import data from "json!example/data.json";
console.log(data, fileContent);
UMD 模块
有些模块被设计成兼容多个模块加载器,或者不使⽤模块加载器(全局变量)。它们以模块为代表。这些库可以通过导⼊的形式或全局变量的形式访问。例如:
注释:export as 使模块不仅能够被导⼊使⽤,也提供了全局注册使⽤的⽅式,但是这两个⽅式之前并不兼容。可以理解为函数的重载,只是⼀个模块有两种使⽤⽅式math-lib.d.ts
export function isPrime(x: number): boolean;
export as namespace mathLib;
之后,这个库可以在某个模块⾥通过导⼊来使⽤:
import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // 错误: 不能在模块内使⽤全局定义。
它同样可以通过全局变量的形式使⽤,但只能在某个脚本(指不带有模块导⼊或导出的脚本⽂件)⾥。
mathLib.isPrime(2);
创建模块结构指导
尽可能地在顶层导出
从你的模块中导出⼀个命名空间就是⼀个增加嵌套的例⼦。
使⽤重新导出进⾏扩展
模块不会像全局命名空间对象那样去合并。
模块⾥不要使⽤命名空间
模块具有其⾃⼰的作⽤域,并且只有导出的声明才会在模块外部可见。记住这点,命名空间在使⽤模块时⼏乎没什么价值。
模块本⾝已经存在于⽂件系统之中,这是必须的。我们必须通过路径和⽂件名到它们,这已经提供了⼀种逻辑上的组织形式。我们可以创建/collections/generic/⽂件夹,把相应模块放在这⾥⾯。
命名空间对解决全局作⽤域⾥命名冲突来说是很重要的。⽐如,你可以有⼀个My.Application.Customer.AddForm和My.Application.Order.AddForm -- 两个类型的名字相同,但命名空间不同。
更多关于模块和命名空间的资料查看
危险信号
以下均为模块结构上的危险信号。重新检查以确保你没有在对模块使⽤命名空间:
⽂件的顶层声明是export namespace Foo { ... }(删除Foo并把所有内容向上层移动⼀层)
⽂件只有⼀个export class或export function(考虑使⽤export default)
多个⽂件的顶层具有同样的export namespace Foo {(不要以为这些会合并到⼀个Foo中!)
注释:在 4.1.2 中,两个export的命名空间会⾃动合并—————————————————————————————————————————————以下内容来源于声明合并原⽂地址
模块扩展
注释:被拓展的⽬标必须是该⽬标的定义所在,像export * from "./StringValidator";这样的重新导出,不存在该⽬标的定义是不能够拓展的。
虽然 JavaScript 不⽀持合并,但你可以为导⼊的对象打补丁以更新它们。让我们考察⼀下这个玩具性的⽰例:
// observable.js
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.js
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}
它也可以很好地⼯作在 TypeScript 中,但编译器对Observable.prototype.map⼀⽆所知。你可以使⽤扩展模块来将它告诉编译器:
// observable.ts stays the same
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());
模块名的解析和⽤import/ export解析模块标识符的⽅式是⼀致的。更多信息请参考。当这些声明在扩展中合并时,就好像在原始位置被声明了⼀样。但是,你不能在扩展中声明
新的顶级声明-仅可以扩展模块中已经存在的声明。
全局扩展
你也以在模块内部添加声明到全局作⽤域中。
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Observable = function () {
// ...
}
全局扩展与模块扩展的⾏为和限制是相同的。—————————————————————————————————————————————以下内容来源于命名空间和模块原⽂地址
模块的取舍
就像每个 JS ⽂件对应⼀个模块⼀样,TypeScript ⾥模块⽂件与⽣成的 JS ⽂件也是⼀⼀对应的。这会
产⽣⼀种影响,根据你指定的⽬标模块系统的不同,你可能⽆法连接多个
模块源⽂件。例如当⽬标模块系统为commonjs或umd时,⽆法使⽤outFile选项,但是在 TypeScript 1.8 以上的版本使⽤outFile当⽬标为amd或system。
注释:UMD 是 AMD、CommonJs、CMD 的融合写法,使⼀个模块能同时被三种加载⽅式加载
注释:CMD 和 AMD 的不同在于 CMD 在需要依赖时执⾏依赖,⽽ AMD 的依赖前置到模块前。Seajs 是 CMD 的实现。
疑问:UMD 的实现是哪个库?system 是⼀种新的模块加载⽅案?和 system.js 是什么关系?
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论