你的位置:首页 > Java教程

[Java教程]【TypeScript】TypeScript 学习 4——模块


前端数据验证在改善用户体验上有很大作用,在学了之前的知识的时候,我们很可能会写出以下代码:

interface StringValidator {  isAcceptable(s: string): boolean;}var lettersRegexp = /^[A-Za-z]+$/;var numberRegexp = /^[0-9]+$/;class LettersOnlyValidator implements StringValidator {  isAcceptable(s: string) {    return lettersRegexp.test(s);  }}class ZipCodeValidator implements StringValidator {  isAcceptable(s: string) {    return s.length === 5 && numberRegexp.test(s);  }}// Some samples to tryvar strings = ['Hello', '98052', '101'];// Validators to usevar validators: { [s: string]: StringValidator; } = {};validators['ZIP code'] = new ZipCodeValidator();validators['Letters only'] = new LettersOnlyValidator();// Show whether each string passed each validatorstrings.forEach(s => {  for (var name in validators) {    console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);  }});

那么这段代码最大的问题是什么呢?一个是没法复用,验证的封装和验证过程在同一个文件,验证的封装已经是可以复用的。另一个是接口和两个实现的类都直接挂接在全局变量上,假如数量一多的话,将会污染整个全局变量。

模块化就是为了解决这一问题而诞生的。

module Validation {  export interface StringValidator {    isAcceptable(s: string): boolean;  }  var lettersRegexp = /^[A-Za-z]+$/;  var numberRegexp = /^[0-9]+$/;  export class LettersOnlyValidator implements StringValidator {    isAcceptable(s: string) {      return lettersRegexp.test(s);    }  }  export class ZipCodeValidator implements StringValidator {    isAcceptable(s: string) {      return s.length === 5 && numberRegexp.test(s);    }  }}// Some samples to tryvar strings = ['Hello', '98052', '101'];// Validators to usevar validators: { [s: string]: Validation.StringValidator; } = {};validators['ZIP code'] = new Validation.ZipCodeValidator();validators['Letters only'] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorstrings.forEach(s => {  for (var name in validators) {    console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);  }});

我们使用 module 关键字来定义模块,用 export 关键字让我们的接口、类等成员对模块外可见。

这样,就只有模块名挂接在全局变量上,最大限度地避免污染全局变量了。

  • 分隔模块到多个文件

随着我们项目的扩展,我们的代码总不可能只写在一个文件里。为了更好地维护项目,我们会将特定功能放到一个文件里,然后加载多个功能实现我们想需要的功能。现在我们先将上面的代码分割到多个文件里。

Validation.ts

module Validation {  export interface StringValidator {    isAcceptable(s: string): boolean;  }}

LettersOnlyValidator.ts

/// <reference path="Validation.ts" />module Validation {  var lettersRegexp = /^[A-Za-z]+$/;  export class LettersOnlyValidator implements StringValidator {    isAcceptable(s: string) {      return lettersRegexp.test(s);    }  }}

ZipCodeValidator.ts

/// <reference path="Validation.ts" />module Validation {  var numberRegexp = /^[0-9]+$/;  export class ZipCodeValidator implements StringValidator {    isAcceptable(s: string) {      return s.length === 5 && numberRegexp.test(s);    }  }}

Test.ts

/// <reference path="Validation.ts" />/// <reference path="LettersOnlyValidator.ts" />/// <reference path="ZipCodeValidator.ts" />// Some samples to tryvar strings = ['Hello', '98052', '101'];// Validators to usevar validators: { [s: string]: Validation.StringValidator; } = {};validators['ZIP code'] = new Validation.ZipCodeValidator();validators['Letters only'] = new Validation.LettersOnlyValidator();// Show whether each string passed each validatorstrings.forEach(s => {  for (var name in validators) {    console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);  }});

在项目中新建好以上四个文件,然后我们编译项目,如果我们代码编写没错的话,是能够编译通过的。另外,我们可以见到后面三个文件开头有类似于 C# 的文档注释,这是告诉 TypeScript 编译器该文件依赖于哪些文件,假如依赖的文件不存在的话,编译就会不通过。当然我们不写也是可以的,只不过编译器在编译时不会帮我们检查,一般来说,还是建议写上。

另外,在引用编译生成的 JavaScript 文件时,我们需要注意好顺序。以上面的代码为例,我们在 Html 代码中已经这么引用。

<script src="Validation.js" type="text/javascript" /><script src="LettersOnlyValidator.js" type="text/javascript" /><script src="ZipCodeValidator.js" type="text/javascript" /><script src="Test.js" type="text/javascript" />

  • 外部模块

在上面的方式中,浏览器会把 4 个 JavaScript 都加载。但某些时候,我们并不需要全部都用上,应该实现按需加载。那么在 TypeScript 中如何实现呢,很简单,只需要稍微修改一下就行。

Validation.ts

export interface StringValidator {  isAcceptable(s: string): boolean;}

LettersOnlyValidator.ts

import validation = require('./Validation');var lettersRegexp = /^[A-Za-z]+$/;export class LettersOnlyValidator implements validation.StringValidator {  isAcceptable(s: string) {    return lettersRegexp.test(s);  }}

ZipCodeValidator.ts

import validation = require('./Validation');var numberRegexp = /^[0-9]+$/;export class ZipCodeValidator implements validation.StringValidator {  isAcceptable(s: string) {    return s.length === 5 && numberRegexp.test(s);  }}

Test.ts

import validation = require('./Validation');import zip = require('./ZipCodeValidator');import letters = require('./LettersOnlyValidator');// Some samples to tryvar strings = ['Hello', '98052', '101'];// Validators to usevar validators: { [s: string]: validation.StringValidator; } = {};validators['ZIP code'] = new zip.ZipCodeValidator();validators['Letters only'] = new letters.LettersOnlyValidator();// Show whether each string passed each validatorstrings.forEach(s => {  for (var name in validators) {    console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);  }});

修改完之后,编译。。。不通过!

看官们可能觉得我坑爹了,辛辛苦苦敲这么一大段后,竟然编译不通过。这里,我们先说说模块加载的两种规范。

CommonJS 规范:

CommonJS 目标在于实现一个类似于 Python、Ruby 等语言的标准库。而 NodeJS 使用的就是这一规范。

var m = require('mod');exports.t = m.something + 1;

AMD 规范:

由于上面的 CommonJS 规范所实现的加载是同步的,但实际上,我们的浏览器更需要的是异步加载,因此 AMD 规范应运而生。

define(["require", "exports", 'mod'], function(require, exports, m) {  exports.t = m.something + 1;});

那么 TypeScript 使用哪种规范呢?答案是两种都可以,看需要选择其中一种。

回到我们的项目,修改项目属性。

QQ截图20150620231137

将这里修改为 AMD 或者 CommonJS,然后就可以通过编译了。

  • Export =

在上面的代码中,我们导出模块的根是文件,因此需要写成 zip.ZipCodeValidator 这种形式,那为什么不简化一下呢?直接用 ZipCodeValidator 多好。Export = 就可以帮助我们做这件事。

Validation.ts

export interface StringValidator {  isAcceptable(s: string): boolean;}

LettersOnlyValidator.ts

import validation = require('./Validation');var lettersRegexp = /^[A-Za-z]+$/;class LettersOnlyValidator implements validation.StringValidator {  isAcceptable(s: string) {    return lettersRegexp.test(s);  }}export = LettersOnlyValidator;

ZipCodeValidator.ts

import validation = require('./Validation');var numberRegexp = /^[0-9]+$/;class ZipCodeValidator implements validation.StringValidator {  isAcceptable(s: string) {    return s.length === 5 && numberRegexp.test(s);  }}export = ZipCodeValidator;

Test.ts

import validation = require('./Validation');import zipValidator = require('./ZipCodeValidator');import lettersValidator = require('./LettersOnlyValidator');// Some samples to tryvar strings = ['Hello', '98052', '101'];// Validators to usevar validators: { [s: string]: validation.StringValidator; } = {};validators['ZIP code'] = new zipValidator();validators['Letters only'] = new lettersValidator();// Show whether each string passed each validatorstrings.forEach(s => {  for (var name in validators) {    console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);  }});

  • 别名

module Shapes {  export module Polygons {    export class Triangle { }    export class Square { }  }}import polygons = Shapes.Polygons;var sq = new polygons.Square(); // Same as 'new Shapes.Polygons.Square()'

这样写 import,下面的代码就可以简写一下。需要注意的是,不支持 require 引入的模块。

  • declare 关键字

有时候我们需要定义全局变量,那么我们就需要增加 declare 关键字。

declare ver myString;

那么 myString 变量就是全局的了。这功能在定义全局模块时很有作用。