TS学习笔记

328 阅读12分钟

什么是typeScript

ts是js的超集

ts优缺点

优点:
    1.开发过程中,发现潜在问题
    2.更好的编辑器自动提示
    3.代码语义更清晰易懂
缺点:
    1.一定的学习成本
    2.周边生态还不是很完善
    3.增加前期的开发成本
  

ts基础环境搭建

     npm i typescript -g
     tsc demo.ts  编译demo.ts文件
     node demo.js  运行编译后的文件
     tsc --init 初始化ts项目,会生成tsconfig.json文件
     // ts-node 编译与运行结合
     npm i ts-node -g  
     ts-node demo.ts 编译文件并执行

ts数据类型

  1. 字符串string
let name: string = "yh";
  1. 数字number

let age: number = 18;
  1. 布尔boolean
let flag: boolean = false;
  1. 数组array
数组类型定义有两种方式
    // 素类型后面接上 `[]`
    let numArr: number[] = [1,2,3];
    // 数组泛型
    let strArr: Array<string> = ['1', '2', '3']
  1. 元组Tuple
元组( Tuple )是一种特殊的数组,表示一个已知数量和类型的数组
let tArr: [number, string] = [1, '2'];
  1. 枚举enum
`enum`类型是对JavaScript标准数据类型的一个补充
// 普通枚举 初始值为0,其余成员会按顺序自动增长
enum Color {
    Red, // 0
    Green, // 1
    Blue // 2
} 
let c: Color = Color.Green; //1

// 设置初始值
enum Color {
    Red = 1 , // 1
    Green, // 2
    Blue // 3
} 
let c: Color = Color.Green; // 2

// 字符串枚举
enum Color {
  Red = "红色",
  Green = "绿色",
  Blue = "蓝色",
}
const RedColor = Color.Red; // 红色
  1. 任意值any

当我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量;比如来自用户输入或第三方代码库

let notSure: any = 4; 
notSure = "maybe a string instead"; 
notSure = false; // okay, definitely a boolean
  1. 空值viod

void类型像是与any类型相反,它表示没有任何类型。 告诉别人这个函数没有返回值

function warnUser(): void { 
    console.log("This is my warning message"); 
}
  1. Null 和 Undefined

默认情况下nullundefined是所有类型的子类型。 就是说你可以把 nullundefined赋值给其他类型的变量。

    // Not much else we can assign to these variables! 
    let u: undefined = undefined;
    let n: null = null;

但是当你在tsconfig.json中设置了'strictNullChecks': true ,nullundefined只能赋值给void和它们各自。

这能避免 很多常见的问题。 也许在某处你想传入一个 stringnullundefined,你可以使用联合类型string | null | undefined。 再次说明,稍后我们会介绍联合类型。

  1. Never

never类型表示的是那些永不存在的值的类型

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

// 返回never的函数必须存在无法达到的终点 
function error(message: string): never {
    // 抛出错误
    throw new Error(message); 
} 
// 推断的返回值类型为never 
function fail() { 
    return error("Something failed"); 
} 
// 返回never的函数必须存在无法达到的终点 function infiniteLoop(): never { 
    // 死循环
    while (true) { } 
}
  1. Object

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

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

接口 interface

接口的作用就是为类型命名和为你的代码或第三方代码定义契约

接口设置可选|可读

interface Person {
  readonly name: string;
  age?: number;
}

可索引的类型

描述那些能够“通过索引得到”的类型,比如a[10]ageMap["daniel"]。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:

interface StringArray {
    [index: number]: string; 
} 
let myArray: StringArray;
myArray = ["Bob", "Fred"]; 
let myStr: string = myArray[0];

描述对象类型

interface LabelledValue {
  label: string;
  color?: string; // 可选属性
  readonly age: number; // 只读属性
}

描述函数类型

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

// 定义接口
interface SearchFunc {
  (source: string, subString: string): boolean;
}
// 使用接口定义函数变量
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}

接口与类型别名的区别

  • 接口和类型别名都可以用来描述对象或函数的类型,只是语法不同
type MyTYpe = {
  name: string;
  say(): void;
}

interface MyInterface {
  name: string;
  say(): void;
}
  • 都允许扩展
//   interface 用 `extends` 来实现扩展
interface MyInterface {
  name: string;
  say(): void;
}

interface MyInterface2 extends MyInterface {
  sex: string;
}

let person:MyInterface2 = {
  name:'树哥',
  sex:'男',
  say(): void {
    console.log("hello 啊,树哥!");
  }
}

//   type 使用 `&` 实现扩展
type MyType = {
  name:string;
  say(): void;
}
type MyType2 = MyType & {
  sex:string;
}
let value: MyType2 = {
  name:'树哥',
  sex:'男',
  say(): void {
    console.log("hello 啊,树哥!");
  }
}

  • type可以声明基本数据类型别名/联合类型/元组等,而interface不行
// 基本类型别名
type UserName = string;
type UserName = string | number;
// 联合类型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
  • interface能够合并声明,而type不行
interface Person {
  name: string
}
interface Person {
  age: number
}
// 此时Person同时具有name和age属性

描述泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

描述类的类型

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

继承接口

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

混合类型

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口继承类

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
    select() { }
}
class Location {

}

在上面的例子里,SelectableControl包含了Control的所有成员,包括私有成员state。 因为 state是私有成员,所以只能够是Control的子类们才能实现SelectableControl接口。 因为只有 Control的子类才能够拥有一个声明于Control的私有成员state,这对私有成员的兼容性是必需的。

在Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上, SelectableControl接口和拥有select方法的Control类是一样的。 Button和TextBox类是SelectableControl的子类(因为它们都继承自Control并有select方法),但Image和Location类并不是这样的。

当你在TypeScript里声明了一个类的时候,实际上同时声明了很多东西。 首先就是类的 实例的类型 我们也创建了一个叫做 构造函数的值。

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

可以使用简单的方式

class Greeter {
    constructor(public greeting: string) {
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

共有属性(public)、私有属性(private)和受保护属性(protected)

所有属性都默认为 public 当成员被标记成 private时,它就不能在声明它的类的外部访问 protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问

把类当做接口使用

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

函数

函数类型

为函数定义类型

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

let myAdd = function(x: number, y: number): number {
    return x + y; 
};

书写完整函数类型

let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { 
        return x + y; 
    };

函数的类型只是由参数类型和返回值组成的

使用接口定义函数

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

interface SearchFunc { 
    (source: string, subString: string): boolean; 
}

let mySearch: SearchFunc; 
mySearch = function(source: string, subString: string) { 
    let result = source.search(subString);
    return result > -1; 
}

可选参数

function buildName(firstName: string, lastName?: string) {
    if (lastName) return firstName + " " + lastName; 
    else return firstName; 
} 
let result1 = buildName("Bob"); // works correctly now let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters 
let result3 = buildName("Bob", "Adams"); // ah, just right

默认参数

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName; 
} 
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith" 
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters 
let result4 = buildName("Bob", "Adams"); // ah, just right

剩余参数

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
} 
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

重载

方法是为同一个函数提供多个函数类型定义来进行函数重载

let 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") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
# 推断类型
如果你在赋值语句的一边指定了类型但是另一边没有类型的话,TypeScript编译器会自动识别出类型:

// myAdd has the full function type let myAdd = function(x: number, y: number): number { return x + y; };

// The parameters x and y have the type number let myAdd: (baseValue: number, increment: number) => number = function(x, y) { return x + y; };


#  类型断言

某些情况下,我们可能比typescript更加清楚的知道某个变量的类型,所以我们可能希望手动指定一个值的类型

类型断言有两种形式

-     尖括号


```js
let someValue: any = "this is a string"; 
let strLength: number = (<string>someValue).length;
  • as
    
let someValue: any = "this is a string"; 
let strLength: number = (someValue as string).length;
# 联合类型
联合类型用`|`分隔,表示取值可以为多种类型中的一种

```js
let status:string|number
status='to be or not to be'
status=1

泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

定义泛型函数

# 我们把这个版本的identity函数叫做泛型,因为它可以适用于多个类型
function identity<T>(arg: T): T {
    return arg;
}

使用泛型函数

  1. 传入所有的参数,包含类型参数
let output = identity<string>("myString");
  1. 利用类型推论
let output = identity("myString");

多个参数

function identity<T,U>(arg: [T,U]): [T,U] {
    return arg;
}

泛型约束

因为泛型 T 不一定包含我们要处理的属性 ,该怎么做呢

这时,我们可以使用extends关键字来对泛型进行约束,保证我们传入的参数都有这个属性

interface Lengthwise {
    length: number; 
} 
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length); // Now we know it has a .length property, so no more error return arg; 
}
loggingIdentity(3); // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});

泛型接口

interface KeyValue<T,U> {
  key: T;
  value: U;
}

const person1:KeyValue<string,number> = {
  key: 'yh',
  value: 18
}
const person2:KeyValue<number,string> = {
  key: 24,
  value: 'wyb'
}

泛型类

class GenericNumber<T> {
  value: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.value = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

泛型类型别名

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

泛型参数的默认参数

function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

泛型工具类型

  // typeof 关键词除了做类型保护,还可以从实现推出类型
  //先定义变量,再定义类型
let person = {
  name: "yh",
  age: 18,
  gender: "female",
};
type People = typeof person;
function getName(person: People): string {
  return person.name;
}
getName(p1);

//  keyof 可以用来获取一个对象接口中的所有 key 值

interface Person {
  name: string;
  age: number;
  gender: "male" | "female";
}

type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';

function getValueByKey(p: Person, key: PersonKey) {
  return p[key];
}
let val = getValueByKey({ name: "yh", age: 18, gender: "female" }, "name");
console.log(val); // yh


//  in  用来遍历枚举类型

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

//  infer 在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;
// infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。

// extends 有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}


//  索引访问操作符 使用 `[]` 操作符可以进行索引访问:

interface Person {
  name: string;
  age: number;
}

type x = Person["name"]; // x is string

高级类型

交叉类型

交叉类型是将多个类型合并为一个类型

interface personA{
  name: string,
  age: number
}
interface personB {
  name: string,
  gender: string
}

let person: personA & personB = { 
    name: "yh",
    age: 18,
    gender: " 女"
};

类型别名

类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型

type Name = string; 
type NameResolver = () => string; 
type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n; 
    } else {
        return n(); 
    } 
}

同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入

type Tree<T> = {
    value: T; 
    left: Tree<T>;
    right: Tree<T>; 
}

类型保护

  1. typeof

function padLeft(value: string, padding: string | number) { 
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value; 
    } if (typeof padding === "string") {
        return padding + value; 
    } 
    throw new Error(`Expected string or number, got '${padding}'.`); 
}

这些 typeof类型保护只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

  1. instanceof
interface Padder { getPaddingString(): string } 
class SpaceRepeatingPadder implements Padder { 
    constructor(private numSpaces: number) { } 
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" "); 
    } 
} 
class StringPadder implements Padder { 
    constructor(private value: string) { } 
    getPaddingString() { return this.value; } 
} 
function getRandomPadder() {
    return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder(" ");
} // 类型为SpaceRepeatingPadder | StringPadder 
    
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) { 
    padder; // 类型细化为'SpaceRepeatingPadder' 
} if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder' 
}
  1. in 关键字
interface InObj1 {
    a: number,
    x: string
}
interface InObj2 {
    a: number,
    y: string
}
function isIn(arg: InObj1 | InObj2) {
    // x 在 arg 打印 x
    if ('x' in arg) console.log('x')
    // y 在 arg 打印 y
    if ('y' in arg) console.log('y')
}
isIn({a:1, x:'xxx'});
isIn({a:1, y:'yyy'});
  1. 自定义类型保护
// num is number就是一个类型谓词
function isNumber(num: any): num is number { 
    return typeof num === 'number'; 
} 
function isString(str: any): str is string{ 
    return typeof str=== 'string'; 
}

内置工具类型

  • Required 将类型的属性变成必选
interface Person {
    name?: string,
    age?: number,
    hobby?: string[]
}

const user: Required<Person> = {
    name: "yh",
    age: 18,
    hobby: ["code"]
}
  • Partial 将所有属性转换为可选属性
interface Person {
    name: string,
    age: number,
}

type User = Partial<Person>

const p1: User={
  name:'yh'
} // 编译正确
  • Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉,剩余的属性构成新的类型

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
  • Extract 和 Exclude 相反,Extract<T,U> 从 T 中提取出 U。
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void
  • Readonly
interface Person {
  name: string;
  age: number;
  gender?: "male" | "female";
}

let p: Readonly<Person> = {
  name: "yh",
  age: 1 8,
  gender: "male",
};
p.age = 11; // error  Cannot assign to 'age' because it is a read-only property.
  • Record Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。
type Property = 'key1'|'key2'
type Person = Record<Property, string>;

const p: Person = {
  key1: "key1",
  key2: "key2",
};
  • Pick

从某个类型中挑出一些属性出来

type Person = {
  name: string;
  age:number;
  gender:string
}

type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }

const user:P1={
  name:'yh',
  age:18
}
  • Omit

与Pick相反,Omit<T,K> 从T中取出除去K的其他所有属性。

interface Person {
  name: string,
  age: number,
  gender: string
}
type P1 = Omit<Person, "age" | "gender">
const user:P1  = {
  name: 'yh'
}
  • NonNullable

去除类型中的 null 和 undefined

type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]
  • ReturnType

用来得到一个函数的返回值类型

type Func = (value: string) => string;
const test: ReturnType<Func> = "1";
  • Parameters

用于获得函数的参数类型所组成的元组类型。

type P1 = Parameters<(a: number, b: string) => void>; // [number, string]
  • InstanceType

返回构造函数类型T的实例类型

class C {
  x = 0;
  y = 0;
}

type D = InstanceType<typeof C>;  // C

命名空间

命名空间一个最明确的目的就是解决重名问题 TypeScript 中命名空间使用 namespace 来定义,语法格式如下:

namespace SomeNameSpaceName {
    export interface ISomeInterfaceName { } 
    export class SomeClassName { } 
}

以上定义了一个命名空间 SomeNameSpaceName,如果我们需要在外部可以调用 SomeNameSpaceName 中的类和接口,则需要在类和接口添加 export 关键字 要在另外一个命名空间调用语法格式为:

SomeNameSpaceName.SomeClassName;

如果一个命名空间在一个单独的 TypeScript 文件中,则应使用三斜杠 /// 引用它,语法格式如下:

/// <reference path = "SomeFileName.ts" />

以下实例演示了命名空间的使用,定义在不同文件中:

IShape.ts 文件代码:

namespace Drawing {
    export interface IShape { draw(); } 
}

Circle.ts 文件代码:

/// <reference path = "IShape.ts" /> 
namespace Drawing {
    export class Circle implements IShape {
        public draw() { 
            console.log("Circle is drawn"); 
                } 
        } 
}

Triangle.ts 文件代码:

/// <reference path = "IShape.ts" /> 
namespace Drawing { 
    export class Triangle implements IShape {
        public draw() {
            console.log("Triangle is drawn"); 
         } 
     }
 }

TestShape.ts 文件代码:

/// <reference path = "IShape.ts" /> 
/// <reference path = "Circle.ts" /> 
/// <reference path = "Triangle.ts" /> 
function drawAllShapes(shape:Drawing.IShape) { 
    shape.draw(); 
 } 
 drawAllShapes(new Drawing.Circle()); 
 drawAllShapes(new Drawing.Triangle());

使用 tsc 命令编译以上代码:

tsc --out app.js TestShape.ts  

得到以下 JavaScript 代码:

/// <reference path = "IShape.ts" /> 
var Drawing; (function (Drawing) {
    var Circle = /** @class */ (function () {
        function Circle() { } 
                Circle.prototype.draw = function () { 
            console.log("Circle is drawn"); 
        }; 
        return Circle; }()); 
        Drawing.Circle = Circle;
    })(Drawing || (Drawing = {})); 
    /// <reference path = "IShape.ts" /> 
    var Drawing; 
    (function (Drawing) {
        var Triangle = /** @class */ (function () { 
        function Triangle() { } 
        Triangle.prototype.draw = function () {
            console.log("Triangle is drawn"); 
            }; 
            return Triangle; 
     }()); 
        Drawing.Triangle = Triangle; 
           })(Drawing || (Drawing = {})); 
        /// <reference path = "IShape.ts" /> 
        /// <reference path = "Circle.ts" /> 
        /// <reference path = "Triangle.ts" /> 
        function drawAllShapes(shape) { 
            shape.draw(); 
        } 
        drawAllShapes(new Drawing.Circle()); 
        drawAllShapes(new Drawing.Triangle());

使用 node 命令查看输出结果为:

$ node app.js
Circle is drawn
Triangle is drawn

嵌套命名空间

命名空间支持嵌套,即你可以将命名空间定义在另外一个命名空间里头。

namespace namespace_name1 { 
    export namespace namespace_name2 {
        export class class_name { } 
    } 
}

成员的访问使用点号 . 来实现,如下实例: Invoice.ts 文件代码:

namespace Runoob {
    export namespace invoiceApp { 
        export class Invoice { 
            public calculateDiscount(price: number) { return price * .40; } 
         } 
    } 
}

InvoiceTest.ts 文件代码:

/// <reference path = "Invoice.ts" /> 
var invoice = new Runoob.invoiceApp.Invoice(); 
console.log(invoice.calculateDiscount(500));

申明文件

TypeScript 作为 JavaScript 的超集,在开发过程中不可避免要引用其他第三方的 JavaScript 的库。虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript 诸如类型检查等特性功能。为了解决这个问题,需要将这些库里的函数和方法体去掉后只保留导出类型声明,而产生了一个描述 JavaScript 库和模块信息的声明文件。通过引用这个声明文件,就可以借用 TypeScript 的各种特性来使用库文件了。

假如我们想使用第三方库,比如 jQuery,我们通常这样获取一个 id 是 foo 的元素:

$('#foo');
// 或
jQuery('#foo'); 

但是在 TypeScript 中,我们并不知道 $ 或 jQuery 是什么东西:

jQuery('#foo');

// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.

这时,我们需要使用 declare 关键字来定义它的类型,帮助 TypeScript 判断我们传入的参数类型对不对:

declare var jQuery: (selector: string) => any;

jQuery('#foo');

declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

上例的编译结果是:

jQuery('#foo');

声明文件

声明文件以 .d.ts 为后缀,例如:

runoob.d.ts

声明文件或模块的语法格式如下:

declare module Module_Name {
}

TypeScript 引入声明文件语法格式:

/// <reference path = " runoob.d.ts" />

当然,很多流行的第三方库的声明文件不需要我们定义了,比如 jQuery 已经有人帮我们定义好了:jQuery in DefinitelyTyped

以下定义一个第三方库来演示:

var testModule;
(function(testModule) {
    var Calc = (function () {
        function Calc() { 
        } 
     }) 
     Calc.prototype.doSum = function (limit) { 
         var sum = 0; 
         for (var i = 0; i <= limit; i++) {
             sum = sum + i; 
         } 
         return sum; 
      } 
      Runoob.Calc = Calc; 
      return Calc; 
  })(testModule || (testModule = {})); 
  var test = new testModule.Calc();

如果我们想在 TypeScript 中引用上面的代码,则需要设置声明文件 Calc.d.ts,代码如下:

declare module testModule {
    export class Calc {
      doSum( limit: number ):number  
    }
}

声明文件不包含实现,它只是类型声明,把声明文件加入到 TypeScript 中:

/// <reference path = "Calc.d.ts" /> 
var obj = new testModule.Calc(); // obj.doSum("Hello"); // 编译错误  doSum参数需要number
console.log(obj.doSum(10));