衍生类型基础学习

衍生类型 #

数组(Array) #

数组基础介绍 #

TypeScriptJavaScript一样可以操作数组元素。

有两种方式可以定义数组。

数组代码示例 #

第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

const list: number[] = [1, 2, 3];

第二种方式是使用数组泛型,Array<元素类型>

const list: Array<number> = [1, 2, 3];

元组(Tuple) #

元组基础介绍 #

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

元组代码示例 #

比如,你可以定义一对值分别为 stringnumber类型的元组。

// 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) #

对象基础介绍 #

object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

对象代码示例 #

使用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

枚举(enums) #

枚举基础介绍 #

使用枚举我们可以定义一些带名字常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。

TypeScript支持数字的和基于字符串枚举

枚举详细内容 #

详细内容请看


联合类型(union types) #

Param Types #

基础说明

联合类型描述的值可以是几种类型之一。我们使用竖线(|)分隔每种类型,因此number | string | boolean值的类型也可以是a numbera stringa 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,我们只能肯定地知道它具有AB都具有的相同成员。在此示例中,Bird有一个名为的成员fly。我们无法确定类型为的变量是否Bird | Fish具有fly方法。如果变量Fish在运行时确实为a ,则调用pet.fly()将失败。


String Literal Types #

字符串文字类型(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'.

可以传递三个允许的字符串中的任何一个,传递其他字符串会抛出错误。


Numeric Literal Types #

也可以指定一个返回值是多个具体数字中的一种。

type rollNumber = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): rollNumber {
  // ...
  return '222';// Type '"222"' is not assignable to type 'rollNumber'.
}

交叉类型(Intersection Types) #

交叉类型联合类型紧密相关,但是使用方式却大不相同。

交叉类型将多种类型组合为一种。这样可以将现有类型加在一起,以获得具有所需所有功能的单个类型。

例如,Person & Serializable & Loggable是所有PersonSerializable 和 的类型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


接口(interface) #

接口基础介绍 #

TypeScript的核心原则之一是类型检查的重点是值的形状。有时称为鸭式打字结构子类型化。在TypeScript中,接口充当命名这些类型的角色,并且是定义代码内契约以及项目外代码契约的有效方法。

TypeScript支持接口用于类型检测和类继承。

接口详细内容 #

详细内容请看


类(Class) #

类基础介绍 #

传统的JavaScript使用函数和基于原型的继承来构建可重用的组件,但是对于程序员来说,使用继承功能,并且对象是从这些中构建对象的面向对象的方法可能感到有点尴尬。

ECMAScript 2015(也称为ECMAScript 6)开始,JavaScript程序员将能够使用这种面向对象的基于的方法来构建其应用程序。

TypeScript中,我们允许开发人员现在使用这些技术,并将其编译为可在所有主要浏览器和平台上使用的JavaScript,而不必等待下一个JavaScript版本。

类详细内容 #

详细内容请看


方法(function) #

声明函数 #

function 关键词 #

// 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),和衍生类型(数组元组对象interfaceClassnamespace),进行相关声明。

const add = function(x: number, y: number){
  return x + y;
};


可选形参 #

TypeScript中,会默认函数每个参数都是必传的。

虽然实参值可以是nullundefined,但是当调用函数时,编译器将检查用户是否为每个参数提供了一个


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。

编译器将构建一个以省略号(...)后给定的名称,作为参数传入的数组,在函数中可以正常使用。


this #

this指向 #

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会警告您,指出thisin this.suits[pickedSuit]是类型any

this参数 #

不幸的是,类型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 回调中的参数 #

this当将函数传递给以后会调用它们的三方库时,会存在可能在in回调中遇到错误。

因为调用回调的三方库只是像普通函数一样调用它,所以this它将为undefined

但是可以通过一些辅助手段,也可以使用this参数来防止回调错误

第一步,库作者需要使用以下注释

this: void表示addClickListener期望onclick是不需要this类型的函数。

interface UIElement {
  addClickListener(onclick: (this: void, e: Event) => void): void;
}

第二步,在调用代码中声明this的类型。

不能指定this类型为HandlerTypeScript会检测到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类型的所有对象之间共享


函数返回值 #

无返回值 #

可以通过voidnever关键词显示声明无返回值,前置

  • 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;
}


返回自定义结构 #

可以返回自定义结构(interfaceClassnamespace),进行相关返回类型声明。

注意实际返回值一定要和声明返回类型是一样的。

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 ...
}

命名空间(namespace) #

前因介绍 #

实际工作中会需要写数据验证器,比如检查网页上表单上用户的输入或检查外部提供的数据文件的格式。

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,LettersOnlyValidatorZipCodeValidator可以直接使用export进行能力输出,外部可以通过Validation.StringValidator进行使用。

相反,变量lettersRegexpnumberRegexp是实现细节,可以不用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" />
    <script src="LettersOnlyValidator.js" type="text/javascript" />
    <script src="ZipCodeValidator.js" type="text/javascript" />
    <script src="Test.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;

通过declareD3放到全局中,也可以通过varimport添加命名空间能力别名。

没有定义实现的声明通常称为环境(ambient),这些定义通常在.d.ts文件中进行声明。如果熟悉C/C++,则可以将它们视为.h文件效果一致。


Build by Loppo 0.6.16