前端數據驗證在改善用戶體驗上有很大作用,在學了之前的知識的時候,我們很可能會寫出以下代碼:
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 try var strings = ['Hello', '98052', '101']; // Validators to use var validators: { [s: string]: StringValidator; } = {}; validators['ZIP code'] = new ZipCodeValidator(); validators['Letters only'] = new LettersOnlyValidator(); // Show whether each string passed each validator strings.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 try var strings = ['Hello', '98052', '101']; // Validators to use var validators: { [s: string]: Validation.StringValidator; } = {}; validators['ZIP code'] = new Validation.ZipCodeValidator(); validators['Letters only'] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator strings.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 try var strings = ['Hello', '98052', '101']; // Validators to use var validators: { [s: string]: Validation.StringValidator; } = {}; validators['ZIP code'] = new Validation.ZipCodeValidator(); validators['Letters only'] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator strings.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 try var strings = ['Hello', '98052', '101']; // Validators to use var validators: { [s: string]: validation.StringValidator; } = {}; validators['ZIP code'] = new zip.ZipCodeValidator(); validators['Letters only'] = new letters.LettersOnlyValidator(); // Show whether each string passed each validator strings.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 使用哪種規范呢?答案是兩種都可以,看需要選擇其中一種。
回到我們的項目,修改項目屬性。
將這里修改為 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 try var strings = ['Hello', '98052', '101']; // Validators to use var validators: { [s: string]: validation.StringValidator; } = {}; validators['ZIP code'] = new zipValidator(); validators['Letters only'] = new lettersValidator(); // Show whether each string passed each validator strings.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 變量就是全局的了。這功能在定義全局模塊時很有作用。