TypeScript
像JavaScript
一样可以操作数组元素。
有两种方式可以定义数组。
第一种,可以在元素类型后面接上 []
,表示由此类型元素组成的一个数组:
const list: number[] = [1, 2, 3];
第二种方式是使用数组泛型,Array<元素类型>
:
const list: Array<number> = [1, 2, 3];
元组
类型允许表示一个已知元素数量和类型
的数组,各元素的类型不必相同。
比如,你可以定义一对值分别为 string
和number
类型的元组。
// Declare a tuple type
const x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
当访问一个已知索引的元素,会得到正确的类型:
const { log } = console;
log(x[0].substring(1)); // OK
log(x[1].substring(1)); // Error, 'number' does not have 'substring'
当访问一个越界的元素,会使用联合类型替代:
const { log } = console;
x[3] = "world"; // Error, Property '3' does not exist on type '[string, number]'.
log(x[5].toString()); // Error, Property '5' does not exist on type '[string, number]'.
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。
使用object
类型,就可以更好的表示像Object.create
这样的API。例如:
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
使用枚举
我们可以定义一些带名字
的常量
。 使用枚举
可以清晰地表达意图或创建一组有区别的用例。
TypeScript支持数字
的和基于字符串
的枚举
。
基础说明
联合类型描述的值可以是几种类型之一。我们使用竖线(|)分隔每种类型,因此number
| string
| boolean
值的类型也可以是a number
,a string
或a boolean
。
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: string | number) {
// ...
}
const indentedString = padLeft("Hello world", true); // errors during compilation
注意事项
如果我们拥有一个具有联合类型
的值,则我们只能访问该联合中所有类型都通用
的成员。
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
const pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
如果值具有类型A | B
,我们只能肯定地知道它具有A
和 B
都具有的相同成员。在此示例中,Bird
有一个名为的成员fly
。我们无法确定类型为的变量是否Bird | Fish
具有fly
方法。如果变量Fish
在运行时确实为a
,则调用pet.fly()
将失败。
字符串文字类型(String Literal Types)与联合类型(union types),类型保护和类型别名很好地结合在一起,将这些功能一起使用,就获得类似于字符串的枚举行为。
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
} else if (easing === "ease-out") {
} else if (easing === "ease-in-out") {
} else {
// error! should not pass null or undefined.
}
}
}
const button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // Argument of type '"uneasy"' is not assignable to parameter of type 'Easing'.
可以传递三个允许的字符串中的任何一个,传递其他字符串会抛出错误。
也可以指定一个返回值是多个具体数字中的一种。
type rollNumber = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): rollNumber {
// ...
return '222';// Type '"222"' is not assignable to type 'rollNumber'.
}
交叉类型
与联合类型
紧密相关,但是使用方式却大不相同。
交叉类型
将多种类型组合为一种。这样可以将现有类型加在一起,以获得具有所需所有功能的单个类型。
例如,Person & Serializable & Loggable
是所有Person
和 Serializable
和 的类型Loggable
。这意味着此类型的对象将具有所有三种类型的所有成员。
function extend<First, Second>(first: First, second: Second): First & Second {
const result: Partial<First & Second> = {};
for (const prop in first) {
if (first.hasOwnProperty(prop)) {
(result as First)[prop] = first[prop];
}
}
for (const prop in second) {
if (second.hasOwnProperty(prop)) {
(result as Second)[prop] = second[prop];
}
}
return result as First & Second;
}
class Person {
constructor(public name: string) {}
}
interface Loggable {
log(name: string): void;
}
class ConsoleLogger implements Loggable {
log(name) {
console.log(`Hello, I'm ${name}.`);
}
}
const jim = extend(new Person("Jim"), ConsoleLogger.prototype);
jim.log(jim.name);
这是一个简单的示例,显示了如何创建一个mixin
。
TypeScript
的核心原则之一是类型检查
的重点是值的形状。有时称为鸭式打字
或结构子类型化
。在TypeScript
中,接口充当命名这些类型的角色,并且是定义代码内契约以及项目外代码契约的有效方法。
TypeScript支持接口用于类型检测和类继承。
传统的JavaScript
使用函数和基于原型的继承来构建可重用的组件,但是对于程序员来说,使用类
继承功能,并且对象是从这些类
中构建对象的面向对象的方法可能感到有点尴尬。
从ECMAScript 2015
(也称为ECMAScript 6
)开始,JavaScript
程序员将能够使用这种面向对象的基于类
的方法来构建其应用程序。
在TypeScript
中,我们允许开发人员现在使用这些技术,并将其编译为可在所有主要浏览器和平台上使用的JavaScript
,而不必等待下一个JavaScript
版本。
// Named function
function add(x: number, y: number) :number{
return x + y;
}
// Anonymous function
const add = function(x: number, y: number) :number{
return x + y;
};
// arrow function
const add = (x: number, y: number) :number =>{
return x + y;
};
函数的类型具有相同的两个部分:参数的类型
和返回类型
。写出整个函数类型时,两个部分都是必需的。
// arrow function
const add: (x: number, y: number) => number = function(
x: number,
y: number
): number {
return x + y;
};
参数类型
可以像参数列表一样写参数类型,为每个参数指定名称
和类型
,也可以将名称写的更语义化,可以提高代码可读性。
// arrow function
const add: (baseValue: number, increment: number) => number = (
x: number,
y: number
): number => {
return x + y;
};
只要将参数类型
排列在一起,就会将其视为函数
的有效类型
,无论在函数类型
中为参数
指定什么名称
。
返回类型
我们通过=>
在参数
和返回类型
之间使用粗箭头(=>
)来弄清楚哪个是返回类型
。如前所述,这是函数类型
的必需部分。
如果函数不写返回类型
,则会默认返回类型
为void
。
类型推导
在处理示例时,即使只有在等式的一侧设置参数类型
和返回类型
,TypeScript
编译器也可以自动推断对应的参数类型
和返回类型
。
// add has the full function type
const add = (x: number, y: number): number =>{
return x + y;
};
// The parameters 'x' and 'y' have the type number
const add: (baseValue: number, increment: number) => number = function(x, y) {
return x + y;
};
这称为上下文类型化
,一种类型推断
,这有助于减少保持程序键入的工作量。
注意事项
值得注意的是,只有参数
和返回
类型构成函数类型
。
实际上,捕获的变量名是不会反映在类型中,只是对应函数的隐藏状态
的一部分,并且不构成其API
。
在方法中的参数类型声明中,可以用基础类型(string
,number
,boolean
,和any
),和衍生类型(数组
,元组
,对象
,interface
,Class
和namespace
),进行相关声明。
const add = function(x: number, y: number){
return x + y;
};
在TypeScript
中,会默认函数每个参数都是必传的。
虽然实参值可以是null
或undefined
,但是当调用函数时,编译器
将检查用户是否为每个参数
提供了一个值
。
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
const result1 = buildName("Bob"); // error, too few parameters
const result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
const result3 = buildName("Bob", "Adams"); // ah, just right
可以在希望成为可选参数的参数末尾添加?
。例如,可以设置上述案例中的lastName
是可选的。
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}
const result1 = buildName("Bob"); // works correctly now
const result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
const result3 = buildName("Bob", "Adams"); // ah, just right
一般在形参定义中,一般是先声明必传参数,最后再写可选形参。
如果上例中firstName
成为可选的,而不是lastName
,则需要更改函数
中参数
的顺序,将firstName
放在列表的最后,即buildName(lastName: string, firstName?: string)
。
基础配置
在TypeScript
中,会默认函数形参数量是固定
的,即预判赋予函数的参数数量
必须与函数期望的参数数量
匹配。
但是,如果形参设置了默认值
或者设置为可选参数
,则在计算形参默认需要传递参数数量时不考虑在内。
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
const result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
const result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
const result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
const result4 = buildName("Bob", "Adams"); // ah, just right
共享类型
可选参数
和尾随默认参数
将在其类型
上共享通用性,因此两者
function buildName(firstName: string, lastName?: string) {
// ...
}
和
function buildName(firstName: string, lastName = "Smith") {
// ...
}
共享同一类型(firstName: string, lastName?: string) => string
。默认值lastName
消失在类型中,仅留下参数是可选的事实。
默认形参顺序
如果默认初始化参数
位于必传参数
之前,则需要显式
传递undefined
以获取默认初始化值
。
如下例中,firstName
是默认初始化参数
,但是它是第一个形参,所以需要显式
传递undefined
,以获取默认初始化值
--Will
。
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
const result1 = buildName("Bob"); // error, too few parameters
const result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
const result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
const result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
有的时候,会需要将多个参数作为一个组来使用,或者可能不知道一个函数最终将使用多少个参数。
在TypeScript
中,可以通过...
将这些参数一起收集到一个变量中,剩余形参
会被视为无数可选参数
。
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
// employeeName will be "Joseph Samuel Lucas MacKinzie"
const employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
在为剩余形参
传递参数时,可以使用任意数量的参数,数量也可以为0。
编译器将构建一个以省略号(...
)后给定的名称,作为参数传入的数组,在函数中可以正常使用。
在js
中默认是this
指向window
对象。在严格模式下,this
将是undefined
而不是window
)。
这样导致在代码在执行过程中,不是按照人预期进行执行的。
const deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
return function() {
const pickedCard = Math.floor(Math.random() * 52);
const pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
const cardPicker = deck.createCardPicker();
const pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);// throw Err
为此,这边推荐将函数表达式更改为使用ECMAScript 6
箭头语法。箭头函数
会捕获this
创建函数的位置,而不是调用函数的位置。
const deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function() {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
const pickedCard = Math.floor(Math.random() * 52);
const pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
const cardPicker = deck.createCardPicker();
const pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
而且如果将--noImplicitThis
标志传递给编译器,当在写这种存在二义性代码的时候,TypeScript
会警告您,指出this
in this.suits[pickedSuit]
是类型any
。
不幸的是,类型this.suits[pickedSuit]
仍然是any
。那是因为this
来自对象文字内部的函数表达式
。
要解决此问题,可以提供一个显式this参数
。
第一步,伪造this参数
,在函数的参数列表
中添加this参数
。
function f(this: void) {
// make sure `this` is unusable in this standalone function
}
第二步,在上面的示例Card
中添加几个接口Deck
,以使类型更清晰,更易于重用:
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
const deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
const pickedCard = Math.floor(Math.random() * 52);
const pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
const cardPicker = deck.createCardPicker();
const pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
然后,TypeScript
编译器这边就知道了,createCardPicker
期望在Deck
对象上被调用。
即现在this
是类型Deck
,不是any
,所以--noImplicitThis
不会引起任何错误。
this
当将函数传递给以后会调用它们的三方库
时,会存在可能在in
回调中遇到错误。
因为调用回调的三方库
只是像普通函数
一样调用它,所以this
它将为undefined
。
但是可以通过一些辅助手段,也可以使用this参数
来防止回调错误
。
第一步,库作者需要使用以下
注释
this: void
表示addClickListener
期望onclick
是不需要this
类型的函数。
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
第二步,在调用代码中声明
this
的类型。
不能指定this
类型为Handler
,TypeScript
会检测到addClickListener
需要具有的函数this: void
。
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used `this` here. using this callback would crash at runtime
this.info = e.message;
}
}
const h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
要解决该错误,请更改this
类型为void
。
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}
const h = new Handler();
uiElement.addClickListener(h.onClickGood);
将onClickGood
其中this
类型指定为void
之后,这边addClickListener
就可以正常调用了。
第三步,配合箭头函数
上述两个步骤还不能正常使用this
属性,如this.info
。
使用箭头功能
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}
之所以有效,是因为箭头函数使用外部函数this
,因此总是可以将它们传递给需要的东西this: void
。
不利的一面
每个Handler
类型的对象,都会需要使用箭头功能。
另一方面,方法仅创建一次,并附加到Handler
的原型,它们在Handler
类型的所有对象之间共享
。
可以通过void
和never
关键词显示声明无返回值,前置
void
类型的函数,只能手动返回undefined
,或者无返回值。
never
类型是那些总是会抛出异常,根本就不会有返回值的函数表达式,和箭头函数表达式的返回值类型。
function error(message: string): never {
throw new Error(message);
}
function testUndefined(): void {
return undefined;
}
可以返回基础类型(string
,number
,boolean
,和any
),和衍生类型(数组
,元组
,和对象
),进行相关返回类型声明。
注意实际返回值一定要和声明返回类型是一样的。
function buildName(firstName = "Will", lastName: string): string {
return firstName + " " + lastName;
}
可以返回自定义结构(interface
,Class
和namespace
),进行相关返回类型声明。
注意实际返回值一定要和声明返回类型是一样的。
interface Name {
firstName: string;
lastName: string;
}
function buildName(firstName = "Will", lastName: string) {
return {firstName, lastName};
}
基础案例
如果当前函数没有指定返回类型,这边在做函数赋值的时候,可以再以显式声明的写法指明返回值。
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
const buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
JavaScript
本质上是一种非常动态的语言。单个JavaScript
函数根据传入参数
的类型,返回不同值的情况并不少见。
const suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
const pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just const them pick the card
else if (typeof x == "number") {
const pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
const myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 }
];
const pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
const pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
上例种pickCard
函数,根据用户传递的内容判断,返回了两种不同的内容。
上述写法,也可以用类型系统
来描述,为同一功能提供多种功能类型,作为重载列表
。
该列表
是编译器将用来解析函数调用的列表
。
如下例中创建一个重载列表
,包括参数类型
和返回类型
的重载,以描述我们pickCard
接受和返回的内容。
const suits = ["hearts", "spades", "clubs", "diamonds"];
// 重载列表
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
// 函数实际声明部分
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
const pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just const them pick the card
else if (typeof x == "number") {
const pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
const myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 }
];
const pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
const pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
通过此更改,解析器可以通过重载列表,对该pickCard
函数进行类型检查
调用。
为了使编译器选择正确的类型检查
,它遵循与基础JavaScript
相似的过程。
编译器会查看重载列表
,并在第一次重载之前,尝试使用提供的参数调用该函数。
如果找到形参类型匹配项
,它将选择此重载类型
作为正确的形参类型
。因此,习惯上将重载从最具体
到最不具体
排序。
请注意,该function pickCard(x): any
片段不是重载列表
的一部分,这是具体的函数声明部分。
pickCard
函数的重载列表
,x
只支持对象
和数字
形式,使用其他任何参数类型进行调用都会导致错误。
基于String Literal Types的方法重载
可以使用相同的方式使用字符串文字类型来区分重载,当传递参数值为某个具体值,也可以具体指定返回类型。
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
实际工作中会需要写数据验证器,比如检查网页上表单上用户的输入或检查外部提供的数据文件的格式。
interface StringValidator {
isAcceptable(s: string): boolean;
}
let lettersRegexp = /^[A-Za-z]+$/;
let 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
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`);
}
}
随着添加更多的验证器,需要一种统一管理方案,以便可以跟踪具体检测的是某一类的类型,而且也不必担心与其他类型校验方法名称冲突。
所以在全局名称空间中不需要放置过多的验证器,而是应将具体类型的校验方法都放到名称空间中。
主要是介绍使用TypeScript
中的namespace(命名空间)
(以前称为内部模块(Internal modules)
)组织代码的各种方法。
另外,module
在声明内部模块时,都可以用namespace
关键字代替。这样可以避免给新用户添加名称相似的术语,对于二义性存在疑惑。
在下面示例中,将所有与验证器
相关的方法和实体,移动到Validation
命名空间中。
在命名空间中可以单独输出对象,比如StringValidator
,LettersOnlyValidator
和ZipCodeValidator
可以直接使用export
进行能力输出,外部可以通过Validation.StringValidator
进行使用。
相反,变量lettersRegexp
和numberRegexp
是实现细节,可以不用export
,即对命名空间
之外的代码不可见。
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const 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);
}
}
}
const { log } = console;
// Some samples to try
const strTestArr = ["Hello", "98052", "101"];
// Validators to use
const validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (const str of strTestArr) {
for (const checkFnName in validators) {
log(`"${str}" - ${validators[checkFnName].isAcceptable(str) ? "matches" : "does not match"} ${checkFnName}`);
}
log('\n');
}
/**
"Hello" - does not match ZIP code
"Hello" - matches Letters only
"98052" - matches ZIP code
"98052" - does not match Letters only
"101" - does not match ZIP code
"101" - does not match Letters only
*/
命名空间这边主要是保证数据和方法的隔离性,将公共能力进行输出,内部实现逻辑就不对外暴露,和匿名函数一部分效果很像。
也可以将多个文件中的能力逻辑归并到一个命名空间中。
比如,可以将Validation
命名空间中能力拆分到多个文件中,也就是说,即使文件是分开的,也可以归并到一个相同的命名空间中。
只需要将<reference path="xx.ts" />
参考标记添加到对应的文件中,告知编译器文件之间的依赖关系,实现效果和全部定义在一个文件内一样,测试代码也不需要更改。
Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const 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" />
const { log } = console;
// Some samples to try
const strTestArr = ["Hello", "98052", "101"];
// Validators to use
const validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (const str of strTestArr) {
for (const checkFnName in validators) {
log(`"${str}" - ${validators[checkFnName].isAcceptable(str) ? "matches" : "does not match"} ${checkFnName}`);
}
log('\n');
}
/**
"Hello" - does not match ZIP code
"Hello" - matches Letters only
"98052" - matches ZIP code
"98052" - does not match Letters only
"101" - does not match ZIP code
"101" - does not match Letters only
*/
一旦涉及到多个文件,我们将需要确保所有已编译的代码都已加载。
有两种方法可以做到这一点。
首先,我们可以使用--outFile
带有标志的级联输出将所有输入文件编译成一个JavaScript
输出文件
tsc --outFile sample.js Test.ts
编译器将根据文件中存在的参考标记
自动排序输出文件,还可以分别指定每个文件
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
另外,我们可以使用按文件编译(默认)为每个输入文件发出一个JavaScript
文件。
如果产生了多个JS
文件,则需要<script>
在网页上使用标签,以适当的顺序加载每个文件。
MyTestPage.html(节选)
<script src="Validation.js" type="text/javascript" />
命名空间
在外部使用的时候,也可以用简单别名进行使用,比如import q = x.y.z
,它为常用对象创建更短的名称。
注意,不要与import x = require("name")
用于加载模块
的语法相混淆,该语法只是为指定对象
创建别名
。
可以对任何类型
的标识符
,使用这些类型
的导入(通常称为别名
),包括从模块
导入创建的对象。
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
import polygons = Shapes.Polygons;
const sq = new polygons.Square(); // Same as 'new Shapes.Polygons.Square()'
注意,我们不使用require
关键字。相反,我们直接从我们要导入的符号
的合格名称中进行分配。
这类似于使用var
,但也适用于导入符号
的类型
和命名空间
含义。
重要的是,对于值,import
是对原始符号
不同的引用
,而var
如果赋值的对象有所更改,将不会反映在对应变量
中。
因为大多数JavaScript
库只公开一些顶级对象
,具体能力都是通过顶级对象
进行获取,所以,可以通过命名空间
声明该库相关公开的API
能力。
比如,D3
是通过<script>
标签(而不是模块加载器
)加载的,可以使用命名空间
来声明输出其能力。
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
}
export interface Event {
x: number;
y: number;
}
export interface Base extends Selectors {
event: Event;
}
}
declare var d3: D3.Base;
通过declare
将D3
放到全局中,也可以通过var
和import
添加命名空间能力别名。
没有定义实现的声明
通常称为环境(ambient)
,这些定义通常在.d.ts
文件中进行声明。如果熟悉C/C++
,则可以将它们视为.h
文件效果一致。