TS中模块和命名空间的简单认知
module
在很多时候,我们称某个文件为一个模块,但是,到底什么样的文件才称之为一个模块,是否有某种”契约关系”。今天看了ts官方文档有点明白了有关模块的这种”契约关系”:
模块是自声明的,两个模块之间的关系是通过在文件级别上使用import和export建立的。说得无耻一点,只要你在文件中使用了import和export语法,就可以将其视为一个模块。
ts官方文档如是说:TypeScript和ECMAScript2015一样,任何包含顶级import或者export的文件都被当成一个模块。其实,也不一定是顶级。以上就是对什么叫模块的简单认知。
export
任何声明:变量、函数、类、接口等都可以通过export关键字导出。这里要特别提醒大家的是,此处的export和CommonJS规范中的exports是万万不同的,不要搞混淆哦。
//导出接口
export interface StringValidator {
isAcceptable(s: string): boolean;
}
//导出变量
export const numberRegexp = /^[0-9]+$/;
//导出类
export class ZipCodeValidator implements StringValidator
{
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s)
}
}
//另一种导出方式
export {StringValidator, numberRegexp, ZipCodeValidator }
//假如在一个模块里,只导出一个类或函数,那么最佳实践就是用默认导出语法
export default function Test (s: string) : boolean {
return s.length === 5
}
import
导入就如同导出一样简单
//the firt way
import{ StringValidator, numberRegexp, ZipCodeValidator } from "./ZipCodeValidator"
//another way
import * as validator from "./ZipCodeValidator"
let zip = new validator.ZipCodeValidator()
//如果是默认导出的话,可以直接这么导入
import test from " 那个默认导出的文件 "
ts中的模块扩展
你可能经常需要去扩展一个模块的功能,在js中我们经常在某个对象上,或者说是原型链上去扩展新功能。而在模块中,最佳实践是导出一个新的实体来提供新的功能。
//Calculator.ts
export class Calculator {
private current = 0;
private memery = 0;
private operator: string;
//charCodeAt() 返回指定位置的字符编码
processDigit(digit: string, currentValue: number){
if(digit >= "0" && digit <= "9"){
return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0))
}
}
}
//现在来扩展它
import { Calculator } from "./Calculator"
class ProgrammerCalculator extends Calculator {
static digits = ["0", "1", "2", "3", "4"];
constructor(public base: number){
super()
if(base <= 0 || base > ProgrammerCalculator.digits.length){
throw new Error ("base has to be within 0 to 16 inclusive")
}
}
protected processDigit(digit: string, currentValue: number){
if(ProgrammerCalculator.digits.indexOf(digit) >= 0 ) {
return currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit)
}
}
}
//导出
export { ProgrammerCalculator as Calculator }
//另:在模块中不要使用命名空间哦!
namespace
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。如果想在命名空间外访问,则用export导出。用关键字namespace来声明命名空间,另,现在仍有很多代码用module来声明命名空间,人呐应当与时俱进,建议用namespace替换掉module,看着顺眼了好多。
分离到多文件
当应用变得很大时,我们需要将代码分离到不同的文件中,以便于维护。这就是传说中的多文件中的命名空间。
//Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s:string): boolean;
}
}
//LettersOnlyValidator.ts
/// <reference path="./Validation" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable( s: string ) : boolean {
return lettersRegexp.test(s);
}
}
}
//ZipCodeValidator.ts
/// <reference path="./Validation.ts" />
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidatior implements StringValidator {
isAcceptable( s: string ) : boolean {
return s.length === 5 && numberRegexp.test(s)
}
}
}
//Test.ts
///<reference path="Validator.ts" />
///<reference path="LettersOnlyValidator.ts" />
///<reference path="ZipCodeValidator.ts" />
let strings = ["Hello", "98502", "101"];
let validators: { [s: string ] : Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
for (let s of strings ) {
for( let name in validators ) {
console.log(""" + s + "" " + (validators[name]).isAcceptable(s) ? "matches" : " does not match " + name)
}
}
//Summery:
//在ts中引用带命名空间的文件,用三斜线指令引入要引用的文件。但貌似在实际开发过程中,我们还是用模块用得比较多。
别名
简化命名空间的操作方法:import q = x.y.z 给常用的对象起一个短的名字。但不要和加载模块的 import x = require(“name”)的语法弄混了,这里的语法只是为指定的符号创建一个别名而已。
namespace Shapes {
export namespace Polygons {
export class Triangle {}
export class Square {}
}
}
import polygons = Shapes.Polygons;
let sq = new polygons.Square();
//当用多层命名空间嵌套的时候,使用此语法会大大提升你的开发效率哦
特别重申: 不要对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突。模块本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。