在 TypeScript 中,就像在 ECMAScript 2015 中一样,任何包含 import 或 export 的文件都被视为模块;相反,没有 import 或 export 的文件被视为其内容在全局范围内可用的脚本。
Non-modules
如果文件中没有 import 或 export 那么它将作为一个js脚本zhon 在Js脚本文件中,变量和类型被声明为在共享全局范围内; 如果有一个不需要导出和导入的文件,但希望ts将其按一个模块处理,可以添加:
export {}
// build
// initWindowVar.ts
// set REGION and ENVIRONMENT
// private block用来区分地区和环境
window.REGION = process.env.SHOPEE_COUNTRY;
window.ENVIRONMENT = process.env.SHOPEE_ENVIRONMENT;
// private block用来判断是否是在admin builder
window.IS_IN_BUILDER = true;
export {};
使用outFile可以将多个脚本合成一个文件,但 module
只能设置为None
, System
, 和 AMD
。
Exporting a declaration
可以用 export
导出 变量,函数,接口,类型别名等。
// StringValidator.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
import { StringValidator } from "./StringValidator";
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
// 重命名
export { ZipCodeValidator as mainValidator };
Re-exports
一个module可以合并导出其他的modules所有导出的变量,方法等。
export * from "module"
// ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && parseInt(s).toString() === s;
}
}
// Export original validator but rename it
export { ZipCodeValidator as RegExpBasedZipCodeValidator } from "./ZipCodeValidator";
export * from "./StringValidator"; // exports 'StringValidator' interface
export * from "./ZipCodeValidator"; // exports 'ZipCodeValidator' class and 'numberRegexp' constant value
export * from "./ParseIntBasedZipCodeValidator"; // exports the 'ParseIntBasedZipCodeValidator' class
// and re-exports 'RegExpBasedZipCodeValidator' as alias
// of the 'ZipCodeValidator' class from 'ZipCodeValidator.ts'
// module.
Import Syntax
可用通过 import
来引用其他模块导出的变量,方法等。
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
// 重命名
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
import as Syntax
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
Import a module for side-effects
模块没有任何导出,或者使用方并不关心模块的导出,只是需要引入一些全局状态
// builder
// 在window上挂载变量
import './initWindowVar';
import default
export default class ZipCodeValidator {
static numberRegexp = /^[0-9]+$/;
isAcceptable(s: string) {
return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
}
}
import validator from "./ZipCodeValidator";
// JQuery.d.ts
declare let $: JQuery;
export default $;
// index.ts
import $ from "jquery";
$("button.continue").html("Next Step...");
Importing Types
typescript 的模块导入导出,除了实现es标准模块的导入导出之后,还实现有它自身基于类型的导入导出,这属于编译阶段的导入导出,在编译完成之后,这个类型的导入导出语句是需要擦除的。
// utils
export const add = (a, b) => {
return a + b;
}
export interface BaseResponse<T = unknown> {
code: number;
data: T;
msg: string;
}
// index.ts
import { add, BaseResponse } from './utils';
const res: BaseResponse<string[]> = {
code: 0,
data: [],
msg: 'success'
}
console.log(add(1,3));
console.log(res);
编译后
// util.js
export const add = (a, b) => {
return a + b;
};
// index.js
import { add } from './utils';
const res = {
code: 0,
data: [],
msg: 'success'
};
console.log(add(1, 3));
console.log(res);
这种处理方式,默认情况下是没问题的,因为ts能够识别哪些导入导出是类型,然后在导出结果中自动擦除。
但是有一些场景中,这个机制有点问题,所以typescript 3.8 新增了 import type
语法 来帮助我们指定导入导出的是类型还是值; 详细可查看 type-only-imports-exports
具体场景
// foo.ts
export interface Options {
label: string;
value: string;
}
// bar.ts
import { Options } from "./foo";
// 单看bar文件 无法分辨 option到底是类型 还是value ?
export {Options};
默认情况下,typescript会在项目范围内分析 模块的内容,上述情况下 我们用tsc
可以正常编译。
编译后
// foo.js
export {};
// bar.js
export {};
但像babel这样的工具,一次这能单个文件;当然,如果配置了isolatedModules tsc
也只会编译单文件。
并且开启了isolatedModules
后,项目的文件必须按模块来处理,但不包括.d.ts
。
在 TypeScript 中,当引用 const 枚举成员时,编译为js文件,其引用会被替换为实际值。
// d.ts
declare const enum Numbers {
Zero = 0,
One = 1,
}
编译后:
console.log(0 /* Numbers.Zero */ + 1 /* Numbers.One */);
像babel这类编译器,只能单个文件处理,所以找不到对应的引用,然后就会导致运行时的错误。
babel编译结果
console.log(Numbers.Zero + Numbers.One);
// 运行报错
通过import type导入的类型在使用时只能当成type使用,不能当成value使用
import type { Base } from "my-library";
import type Foo, { Bar, Baz } from "some-module";
// ~~~~~~~~~~~~~~~~~~~~~~
// error! A type-only import can specify a default import or named bindings, but not both.
let baseConstructor: typeof Base;
// ~~~~
// error! 'Base' only refers to a type, but is being used as a value here.
declare class Derived extends Base {
// ~~~~
// error! 'Base' only refers to a type, but is being used as a value here.
}
// animal.ts
export class Animal {
name: string
}
// comsumer.ts
import type {Animal} from "./animal"
let animal = new Animal()
// ~~~~~~
// 'Animal' cannot be used as a value because it was imported using 'import type'.
export = and import = require()
ts中也可支持commonJs规范, 需要将tsconfig中的 module设为CommonJS
// ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export = ZipCodeValidator;
// index.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"}`
);
});
可以设置 esModuleInterop: true
就可以直接使用普通的import语法
import zip from './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"}`
);
});
Module Output Options
typescript 支持的module输出格式 如下
Module Optional Loading
有些模块并不是页面初始就要执行,所以为了提升页面的加载速度,我们需要对这样的模块做到按需加载。
可以采用 import id = require("...")
语法来完成模块的按需加载
// ZipCodeValidator.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
// index.ts
declare function require(moduleName: string): any;
import { ZipCodeValidator as Zip } from "./ZipCodeValidator";
const needZipValidation = false;
if (needZipValidation) {
// 确保类型类型安全,必须带上typeof
let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
let validator = new ZipCodeValidator();
if (validator.isAcceptable("...")) {
/* ... */
}
}
编译后
// ZipCodeValidator.js
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator {
isAcceptable(s) {
return s.length === 5 && numberRegexp.test(s);
}
}
// index.js
const needZipValidation = false;
if (needZipValidation) {
let ZipCodeValidator = require("./ZipCodeValidator");
let validator = new ZipCodeValidator();
if (validator.isAcceptable("...")) {
/* ... */
}
}
export {};
Re-export to extend
当我们需要对模块内容进行扩展时,一种常见的js解决方案是 mixin
。但是typescript不推荐修改原来的模块,可以通过像继承这样的方式来维护一个新的模块。
export class Calculator{
evaluate() {
console.log('-Calculator --')
}
}
export class NewCalculator extends Calculator {
evaluate() {
console.log('-NewCalculator --')
}
}
Module Resolution
typescript 有两种模块查找的策略:Classic 和 Node. 如果按以上两种策略找不到模块,则会根据项目的类型声明文件来查找模块。 具体可查看ambient module declaration
可以在.d.ts
文件,声明模块
可通过moduleResolution 来配置ts的模块解析策略。
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export var sep: string;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.module.less' {
const classes: { [key: string]: string };
export default classes;
}
// umd可以导出一个全局命名空间
export function isPrime(x: number): boolean;
export as namespace mathLib;
最后,如果编译器无法解析模块,它抛出错误。
Classic
typescript 默认的解析策略
相对路径
import { b } from "./moduleB";
// 相对当前文件来查找modulesB, 假设当前文件路径是 '/root/src/folder/A.ts'
// 按以下路径查找
// root/src/folder/moduleB.ts
// root/src/folder/moduleB.d.ts
非相对路径
import { b } from "moduleB";
// 查找范围
/root/src/folder/moduleB.ts
/root/src/folder/moduleB.d.ts
/root/src/moduleB.ts
/root/src/moduleB.d.ts
/root/moduleB.ts
/root/moduleB.d.ts
/moduleB.ts
/moduleB.d.ts
Node
TypeScript 将模仿 Node.js 运行时解析策略,以便在编译时定位模块的定义文件。 为此,TypeScript 将 TypeScript 源文件扩展名(.ts、.tsx 和 .d.ts)然后按照 Node 的解析逻辑来查找模块。 TypeScript 还将使用 package.json 中名为 types 的字段来代替 ‘main’字段的作用。
相对路径
import { b } from "./moduleB"
// /root/src/moduleA.ts
// 查找范围
/root/src/moduleB.ts
/root/src/moduleB.tsx
/root/src/moduleB.d.ts
/root/src/moduleB/package.json (if it specifies a types property)
/root/src/moduleB/index.ts
/root/src/moduleB/index.tsx
/root/src/moduleB/index.d.ts
非相对路径
import { b } from "moduleB"
// source file: /root/src/moduleA.ts
// 查找范围
/root/src/node_modules/moduleB.ts
/root/src/node_modules/moduleB.tsx
/root/src/node_modules/moduleB.d.ts
/root/src/node_modules/moduleB/package.json (if it specifies a types property)
/root/src/node_modules/@types/moduleB.d.ts
/root/src/node_modules/moduleB/index.ts
/root/src/node_modules/moduleB/index.tsx
/root/src/node_modules/moduleB/index.d.ts
/root/node_modules/moduleB.ts
/root/node_modules/moduleB.tsx
/root/node_modules/moduleB.d.ts
/root/node_modules/moduleB/package.json (if it specifies a types property)
/root/node_modules/@types/moduleB.d.ts
/root/node_modules/moduleB/index.ts
/root/node_modules/moduleB/index.tsx
/root/node_modules/moduleB/index.d.ts
/node_modules/moduleB.ts
/node_modules/moduleB.tsx
/node_modules/moduleB.d.ts
/node_modules/moduleB/package.json (if it specifies a types property)
/node_modules/@types/moduleB.d.ts
/node_modules/moduleB/index.ts
/node_modules/moduleB/index.tsx
/node_modules/moduleB/index.d.ts
Tracing module resolution
可以使用 tsc --traceResolution
来跟踪模块的解析过程。
BaseUrl
可以使用baseUrl 来定义模块的起始查找路径
未定义baseUrl
// 查找过程
/module-in-ts/src/module-reslution/index.ts
/module-in-ts/src/module-reslution/node_modules
/module-in-ts/src/node_modules
/module-in-ts/node_modules/axios/package.json
/module-in-ts/node_modules/axios.ts (.tsx , .d.ts)
...
/module-in-ts/node_modules/axios/index.d.ts
定义baseUrl
在tsconfig.json定义 baseUrl: './'
// 查找过程
/module-in-ts/axios.ts (.tsx, .d.ts)
/module-in-ts/node_modules/axios/package.json
/module-in-ts/node_modules/axios.ts (.tsx , .d.ts)
...
/module-in-ts/node_modules/axios/index.d.ts
path
可通过path配置模块路径映射
"paths": {
"@/*": [
"src/*"
]
}
path 是相对于baseUrl的
Virtual Directories
如果不同层级的目录,最后打包时会打到同级目录。可以考虑使用虚拟目录来通过ts的编译 www.typescriptlang.org/docs/handbo…