TypeScript小結(jié)

日期:2023-08-07

TypeScript小結(jié)

快速搭建TypeScript開發(fā)環(huán)境

首先绪商,全局安裝TypeScript况脆。

npm install typescript -g

安裝完成后可以通過tsc -v檢查版本柒爵。

創(chuàng)建demo項(xiàng)目,npm init -ytsc --init命令來初始化項(xiàng)目目錄彤叉,初始化好后會(huì)生成package.jsontsconfifig.json這兩個(gè)文件庶柿。

package.json是npm項(xiàng)目包管理文件,tsconfifig.jsontypescript的配置文件秽浇。

新建index.ts文件浮庐,然后使用tsc index.ts命令來編譯,會(huì)在當(dāng)前文件下新建一個(gè)index.js文件柬焕。(-w啟動(dòng)熱編譯)

基本數(shù)據(jù)類型

TypeScript 支持與 JavaScript 幾乎相同的數(shù)據(jù)類型审残,此外還提供了實(shí)用的枚舉類型方便我們使用。

boolean

let isDone: boolean = false;

number

JavaScript 一樣斑举,TypeScript 里的所有數(shù)字都是浮點(diǎn)數(shù)搅轿。 這些浮點(diǎn)數(shù)的類型是 number。 除了支持十進(jìn)制和十六進(jìn)制字面量富玷,TypeScript 還支持 ECMAScript 2015 中引入的二進(jìn)制和八進(jìn)制字面量璧坟。

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

string

JavaScript 一樣,可以使用雙引號(hào)( ")或單引號(hào)(')表示字符串赎懦。

let name: string = "bob";
name = "smith";

同樣也可以使用 字符串模板

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.

I'll be ${ age + 1 } years old next month.`;

array

有兩種方式定義數(shù)組雀鹃,第一種,在數(shù)組元素類型后面使用 []

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

第二種励两,使用數(shù)組泛型黎茎,Array<元素類型>

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

Tuple

Tuple 類型也是一個(gè)數(shù)組沼填,我們可以用它來表示一個(gè)已知元素?cái)?shù)量和元素類型的數(shù)組熄云。 比如物臂,你可以定義一對(duì)值分別為 stringnumber類型的元組修陡。

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error

當(dāng)訪問一個(gè)已知索引的元素寞冯,會(huì)得到正確的類型:

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'

使用索引進(jìn)行越界訪問:

x[3] = 'world'; // Error幼驶, Tuple type '[string, number]' of length '2' has no element at index '2'.

調(diào)用數(shù)組的方法:

x.push("world"); // OK
x.push(true); // Error, Argument of type 'true' is not assignable to parameter of type 'string | number'.

1床绪、使用索引來訪問越界元素竿开,編譯器會(huì)報(bào)錯(cuò)誤

2焙畔、使用 push 方法新增元素掸读,元素的類型必須滿足其聯(lián)合類型

enum

enum 類型是對(duì) javascript 標(biāo)準(zhǔn)數(shù)據(jù)類型的一個(gè)補(bǔ)充。

enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

默認(rèn)情況下,枚舉成員從 0 開始賦值儿惫,每次遞增步長為 1澡罚,同時(shí),可以從值到名進(jìn)行反向映射:

// key -> value
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

// value -> key
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

同時(shí)肾请,我們也可以對(duì)枚舉項(xiàng)進(jìn)行手動(dòng)賦值留搔,當(dāng)值為 number 類型時(shí),未賦值的枚舉項(xiàng)會(huì)接著上一個(gè)枚舉項(xiàng)依次賦值铛铁。

enum Days { Sun = 2, Mon, Tue = 5, Wed, Thu, Fri, Sat };

console.log(Days.Sun);  // 2
console.log(Days.Mon);  // 3
console.log(Days.Tue);  // 5
console.log(Days.Wed);  // 6
console.log(Days.Thu);  // 7

如果枚舉項(xiàng)的值有重復(fù)的話隔显,typescript 不會(huì)提示錯(cuò)誤,但是通過 value 獲取 key 的話饵逐,key 是最后一次的枚舉項(xiàng):

enum Days { Sun = 2, Mon = 2, Tue = 1, Wed, Thu, Fri, Sat };
console.log(Days[2]); // Wed

在使用的時(shí)候括眠,最好不要出現(xiàn)覆蓋的情況。

手動(dòng)賦值的枚舉項(xiàng)可以不是 number 類型倍权,但是掷豺,緊跟著的枚舉項(xiàng)必須給初始值,否則會(huì)報(bào)錯(cuò)薄声。

enum Days { Sun = "s", Mon = 2, Tue = 1, Wed, Thu, Fri, Sat };

any

any 表示可以賦值為任意類型当船。

let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

針對(duì)未聲明類型的變量,它會(huì)被識(shí)別為 any

let something;
something = 'seven';
something = 7;

unknown

就像所有類型都可以賦值給 any 默辨,所有類型也都可以賦值給 unknown 德频。使得 unknown 成為TypeScript 類型系統(tǒng)的另?種頂級(jí)類型(另?種是 any )。unknown 類型只能被賦值給 any 類型和 unknown 類型本身缩幸。

let value: unknown;
value = true;
value = 123;
value = [];
//...

let value0: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
//...

void

某種程度上來說壹置,void類型像是與any類型相反,它表示沒有任何類型桌粉。當(dāng)一個(gè)函數(shù)沒有返回值時(shí)蒸绩,你通常會(huì)見到其返回值類型是 void

function bar(): void {}

null和undefined類型

let u: undefined = undefined;
let n: null = null;

Never

表示的類型是那些總是會(huì)拋出異常或根本就不會(huì)有返回值的函數(shù)表達(dá)式或箭頭函數(shù)表達(dá)式的返回值類型铃肯。

// 返回never的函數(shù)必須存在?法達(dá)到的終點(diǎn)
function error(message: string): never {
    throw new Error(message);
}
function infiniteLoop(): never {
    while (true) { }
}

類型推論

如果沒有明確的指定類型,那么 TypeScript 會(huì)依照類型推論(Type Inference)的規(guī)則推斷出一個(gè)類型传蹈。

什么是類型推論

以下代碼雖然沒有指定類型押逼,但是會(huì)在編譯的時(shí)候報(bào)錯(cuò):

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // error TS2322: Type '7' is not assignable to type 'string'.

事實(shí)上,它等價(jià)于:

let myFavoriteNumber: string = 'seven'惦界;
myFavoriteNumber = 7;

TypeScript 會(huì)在沒有明確的指定類型的時(shí)候推測(cè)出一個(gè)類型挑格,這就是類型推論。

如果定義的時(shí)候沒有賦值沾歪,不管之后有沒有賦值漂彤,都會(huì)被推斷成 any 類型而完全不被類型檢查

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

聯(lián)合類型

聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

聯(lián)合類型使用 | 分隔每個(gè)類型。

訪問聯(lián)合類型的屬性和方法

當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候挫望,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法

function getLength(something: string | number): number {
  return something.length;
}
// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

上例中立润,length 不是 stringnumber 的共有屬性,所以編譯器報(bào)錯(cuò)媳板。

訪問 stringnumber 的共有屬性是沒問題的:

function getString(something: string | number): string {
  return something.toString();
}

聯(lián)合類型的變量在被賦值的時(shí)候桑腮,會(huì)根據(jù)類型推論的規(guī)則推斷出一個(gè)類型:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length);
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // error TS2339: Property 'length' does not exist on type 'number'.

在上例中,第 2 行 myFavoriteNumber 被推斷成 string 類型蛉幸,因此訪問其 length 屬性不會(huì)報(bào)錯(cuò)破讨。而第 4 行被推斷成 number,訪問 length 就報(bào)錯(cuò)了奕纫。

類型斷言

類型斷言(Type Assertion)可以用來手動(dòng)指定一個(gè)值的類型提陶。

語法

<type> value 

// or

value as type

tsx 中必須使用后面一種。

前面在聯(lián)合類型中我們提到過匹层,當(dāng) Typescript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候搁骑,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法

function getLength(something: string | number): number {
  return something.length;
}

// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

而有時(shí)候,我們確實(shí)需要在還不確定類型的時(shí)候就訪問其中一個(gè)類型的屬性或方法又固,比如:

function getLength(something: string | number): number {
  if (something.length) {
    return something.length;
  } else {
    return something.toString().length;
  }
}

// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

在上例中仲器,訪問 something.length 的時(shí)候會(huì)報(bào)錯(cuò),因?yàn)?length 并不是公共屬性仰冠。此時(shí)乏冀,我們就可以使用類型斷言,將 something 斷言成 string

function getLength(something: string | number): number {
  if ((<string>something).length) {
    return (something as string).length;
  } else {
    return something.toString().length;
  }
}

類型斷言不是類型轉(zhuǎn)換洋只,斷言成一個(gè)聯(lián)合類型中不存在的類型是不允許的

function toBoolean(something: string | number): boolean {
  return <boolean>something;
}

// error TS2352: Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'number' is not comparable to type 'boolean'.

類型別名

類型別名用來給一個(gè)類型起個(gè)新名字辆沦,常用語聯(lián)合類型。

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

字符串字面量類型

字符串字面量類型用來約束取值只能是某幾個(gè)字符串中的一個(gè)识虚。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element | null , event: EventNames) {
  // do something
}

handleEvent(document.querySelector('hello'), 'scroll');
handleEvent(document.querySelector('world'), 'dbclick'); // error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

上例中肢扯,我們使用 type 定了一個(gè)字符串字面量類型 EventNames,它只能取三種字符串中的一種担锤。

類型別名與字符串字面量類型都是使用 type 進(jìn)行定義蔚晨。

函數(shù)

聲明式函數(shù)

一個(gè)函數(shù)有輸入和輸出,要在 TypeScript 中對(duì)其進(jìn)行約束肛循,需要把輸入和輸出都考慮到铭腕,其中函數(shù)聲明的類型定義較簡單:

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

輸入多余的(或者少于要求的)參數(shù),都是不被允許的多糠。

sum(1, 2, 3); // error TS2554: Expected 2 arguments, but got 3.
sum(1); //Expected 2 arguments, but got 1.

函數(shù)表達(dá)式

如果要我們現(xiàn)在寫一個(gè)對(duì)函數(shù)表達(dá)式(Function Expression)的定義累舷,可能會(huì)寫成這樣:

const sum = (x: number, y: number): number => x + y;

這是可以通過編譯的,不過事實(shí)上夹孔,上面的代碼只對(duì)等號(hào)右側(cè)的匿名函數(shù)進(jìn)行類型定義被盈,而等號(hào)左邊的 sum析孽,是通過賦值操作進(jìn)行 類型推論 推斷出來的。如果我們需要手動(dòng)給 sum 添加類型只怎,則應(yīng)該是這樣:

const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;

不要混淆了 TypeScript 中的 =>ES6 中的 =>袜瞬。

TypeScript 的類型定義中,=> 用來表示函數(shù)的定義尝盼,左邊是輸入類型吞滞,需要用括號(hào)括起來,右邊是輸出類型盾沫。

使用接口定義函數(shù)類型

我們可以通過接口來定義函數(shù)的類型:

interface ISum {
  (x: number, y: number): number
}

const sum: ISum = (x, y) => x + y;

可選參數(shù)

前面提到裁赠,輸入多余的(或者少于要求的)參數(shù),是不允許的赴精。那么如何定義可選的參數(shù)呢佩捞?

與接口中的可選屬性類似,我們用 ? 表示可選的參數(shù):

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}
let tomcat: string = buildName('Tom', 'Cat');
let tom: string = buildName('Tom');

需要注意的是蕾哟,可選參數(shù)必須接在確定參數(shù)后面一忱。換句話說,可選參數(shù)后面不允許再出現(xiàn)確定參數(shù)谭确。

function buildName(firstName?: string, lastName: string) {
  if (firstName) {
    return firstName + ' ' + lastName;
  } else {
    return lastName;
  }
}
// error TS1016: A required parameter cannot follow an optional parameter.

參數(shù)默認(rèn)值

ES6 中帘营,我們?cè)试S給函數(shù)的參數(shù)添加默認(rèn)值,TypeScript 會(huì)將添加了默認(rèn)值的參數(shù)識(shí)別為可選參數(shù)

function buildName(firstName: string, lastName: string = 'Cat') {
  return firstName + ' ' + lastName;
}

此時(shí)就不受「可選參數(shù)必須接在必需參數(shù)后面」的限制了:

function buildName(firstName: string = 'Tom', lastName: string) {
  return firstName + ' ' + lastName;
}

剩余參數(shù)

ES6 中逐哈,可以使用 ...rest 的方式獲取函數(shù)中的剩余參數(shù)(rest 參數(shù))

function push(array, ...items) {
  items.forEach(function (item) {
    array.push(item);
  });
}

事實(shí)上芬迄,items 是一個(gè)數(shù)組,所以我們可以用數(shù)組的類型來定義:

function push<A, B>(array: A[], ...items: B[]): void {
  items.forEach(item => {
    console.log(item);
  })
}

重載

重載允許一個(gè)函數(shù)接收不同數(shù)量或類型的參數(shù)時(shí)昂秃,作出不同的處理禀梳。

比如,我們需要實(shí)現(xiàn)一個(gè)函數(shù) reverse肠骆,輸入數(shù)字 123 時(shí)算途,返回反轉(zhuǎn)的數(shù)字 321,輸入字符串 hello 時(shí)蚀腿,返回反轉(zhuǎn)的字符串 olleh嘴瓤,利用聯(lián)合類型,我們可以這樣實(shí)現(xiàn):

type Reverse = string | number;

function reverse(x: Reverse): Reverse {
  if (typeof x === "number") {
    return Number(x.toString().split('').reverse().join(''));
  } else {
    return x.split('').reverse().join('');
  }
}

然而這樣做有一個(gè)缺點(diǎn)唯咬,就是不能 精確 的表達(dá)纱注,輸入數(shù)字的時(shí)候,返回也是數(shù)字胆胰,輸入字符串的時(shí)候,也應(yīng)該返回字符串刻获。這時(shí)蜀涨,我們可以使用重載定義多個(gè) reverse 函數(shù)類型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string) {
  if (typeof x === "number") {
    return Number(x.toString().split('').reverse().join(''));
  } else {
    return x.split('').reverse().join('');
  }
}

以上代碼瞎嬉,我們重復(fù)多次定義了 reverse 函數(shù),前幾次都是函數(shù)的定義厚柳,最后一次是函數(shù)的實(shí)現(xiàn)氧枣,這時(shí),在編譯階段的提示中别垮,就可以正確的看到前兩個(gè)提示了便监。

TypeScript 會(huì)優(yōu)先從最前面的函數(shù)定義開始匹配,所以多個(gè)函數(shù)定義如果有包含關(guān)系碳想,需要優(yōu)先把精確的定義寫在前面烧董。

接口

typescript 中,我們可以使用 interface 來定義復(fù)雜數(shù)據(jù)類型胧奔,用來描述形狀或抽象行為逊移。如:

interface IPerson {
  name: string;
  age: number;
  sayName(): void;
}

const p: IPerson = {
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

接口名稱首字母大寫,同時(shí)加上 I 前綴龙填。

變量 p 的類型是 IPerson胳泉,這樣就約束了它的數(shù)據(jù)結(jié)構(gòu)必須和 IPerson 保持一致,多定義和少定義都是不被允許的岩遗。

賦值的時(shí)候扇商,變量的形狀必須和接口的形狀保持一致

可選屬性

有時(shí)宿礁,我們希望不要完全匹配接口中的屬性案铺,那么可以用可選屬性:

interface IPerson {
  name: string;
  age: number;
  gender?: string; // 可選屬性
  sayName(): void;
}

const p: IPerson = {
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

在進(jìn)行賦值時(shí), gender 屬性是可以不存在的窘拯。當(dāng)然红且,這時(shí)仍然不允許添加接口中未定義的屬性。

只讀屬性

有時(shí)候我們希望對(duì)象中的一些屬性只能在創(chuàng)建的時(shí)候被賦值涤姊,那么可以用 readonly 定義只讀屬性:

interface IPerson {
  readonly id: number;        // 只讀屬性
  name: string;
  age: number;
  gender?: string;
  sayName(): void;
}

只讀約束存在于第一次給對(duì)象賦值的時(shí)候暇番,而不是第一次給只讀屬性賦值的時(shí)候。 因此思喊,在對(duì)象初始化的時(shí)候壁酬,必須賦值,之后恨课,這個(gè)屬性就不能再賦值舆乔。

const p: IPerson = {
  id: 1,
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

const vs readonly:變量用 const,對(duì)象屬性用 readonly

任意屬性

有時(shí)候剂公,我們希望一個(gè)接口允許有任意屬性:

interface IPerson {
  readonly id: number;
  name: string;
  age: number;
  gender?: string;
  sayName(): void;
  [propsName: string]: any; // 任意屬性
}

[propsName: string]: any;通過 字符串索引簽名 的方式希俩,我們就可以給 IPerson 類型的變量上賦值任意數(shù)量的其他類型。

const p: IPerson = {
  id: 1,
  name: "tom",
  age: 21,
  email: "102376640@qq.com", // 任意屬性
  phone: 1234567890, // 任意屬性
  sayName() {
    console.log(this.name);
  },
};

emailphone 屬性沒有在 IPerson 中顯性定義纲辽,但是編譯器不會(huì)報(bào)錯(cuò)颜武,這是因?yàn)槲覀兌x了字符串索引簽名璃搜。

一旦定義字符串索引簽名,那么接口中的確定屬性和可選屬性的類型必須是索引簽名類型的子集鳞上。

interface IPerson {
    name: string;
    age?: number;
    [propName: string]: string;
}

// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.ts(2411)
// (property) IPerson.age?: number | undefined

[propName: string]: string;字符串索引簽名類型為 string这吻,但是可選屬性 agenumber 類型,number 并不是 string 的子集篙议, 因此編譯報(bào)錯(cuò)唾糯。

表示數(shù)組

接口除了可以用來描述對(duì)象以外,還可以用來描述數(shù)組類型鬼贱,也就是數(shù)字索引簽名:

interface NumberArray {
  [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

變量 fibonacci 的類型是 NumberArray移怯,如果還想調(diào)用數(shù)組的方法,則:

interface NumberArray<T> extends Array<T> {
  [index: number]: T;
}
let fibonacci: NumberArray<number> = [1, 1, 2, 3, 5];

表示函數(shù)

接口還可以用來描述函數(shù)吩愧,約束參數(shù)的個(gè)數(shù)芋酌,類型以及返回值:

interface ISearchFunc {
  (source: string, subString: string): boolean
}

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

修飾符

TypeScript 可以使用三種訪問修飾符(Access Modifiers),分別是 public雁佳、privateprotected脐帝。

  • public 修飾的屬性或方法是公有的,可以在任何地方被訪問到糖权,默認(rèn)所有的屬性和方法都是 public****的

  • private 修飾的屬性或方法是私有的堵腹,不能在聲明它的類的外部訪問

  • protected 修飾的屬性或方法是受保護(hù)的,它和 private 類似星澳,區(qū)別是它在子類中也是允許被訪問的

例子:

class Animal {
  public name: string;
  public constructor(name: string) {
    this.name = name;
  }
}

let a = new Animal('Jack');
console.log(a.name);
a.name = 'Tom';
console.log(a.name);

上面的例子中疚顷,name 被設(shè)置為 public,所以直接訪問實(shí)例的 name 屬性是允許的禁偎。如果希望 name不被外部訪問腿堤,這時(shí)候就可以用 private

class Animal {
  private name: string;
  public constructor(name: string) {
    this.name = name;
  }
}

let a = new Animal('Jack');
console.log(a.name); // error TS2341: Property 'name' is private and only accessible within class 'Animal'.
a.name = 'Tom'; // error TS2341: Property 'name' is private and only accessible within class 'Animal'.
console.log(a.name); // error TS2341: Property 'name' is private and only accessible within class 'Animal'.

使用 private 修飾的屬性或方法,在子類中也是不允許訪問的:

class Cat extends Animal {
  constructor(name: string) {
    super(name);
    console.log(this.name); // error TS2341: Property 'name' is private and only accessible within class 'Animal'.
  }
}

如果使用 protected 修飾如暖,則允許在子類中訪問:

class Animal {
  protected name: string;
  public constructor(name: string) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
    console.log(this.name);
  }
}

抽象類

abstract 用于定義抽象類和其中的抽象方法笆檀,抽象類是不允許被實(shí)例化的:

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract sayHello(): void;
  sayName() {
    console.log(this.name);
  }
}

new Animal("Jack"); // error TS2511: Cannot create an instance of an abstract class.

其次,抽象類中的抽象方法盒至,必須被子類實(shí)現(xiàn):

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract sayHello(): void;
  sayName() {
    console.log(this.name);
  }
}

class Cat extends Animal {
  sayHello(): void {
    console.log("hello");
  }
}

const cat: Cat = new Cat("Tom");
cat.sayName();        // ok
cat.sayHello();        // ok

類與接口

實(shí)現(xiàn)接口

實(shí)現(xiàn)(implements)是面向?qū)ο笾械囊粋€(gè)重要概念酗洒。一般來講,一個(gè)類只能繼承自另一個(gè)類枷遂,有時(shí)候不同類之間可以有一些共有的特性樱衷,這時(shí)候就可以把特性提取成接口(interfaces),用 implements 關(guān)鍵字來實(shí)現(xiàn)酒唉。這個(gè)特性大大提高了面向?qū)ο蟮撵`活性矩桂。

舉例來說,門是一個(gè)類痪伦,防盜門是門的子類耍鬓。如果防盜門有一個(gè)報(bào)警器的功能阔籽,我們可以簡單的給防盜門添加一個(gè)報(bào)警方法流妻。這時(shí)候如果有另一個(gè)類牲蜀,車,也有報(bào)警器的功能绅这,就可以考慮把報(bào)警器提取出來涣达,作為一個(gè)接口,防盜門和車都去實(shí)現(xiàn)它:

interface Alarm {
  alert(): void;
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
  alert() {
    console.log('SecurityDoor alert');
  }
}

class Car implements Alarm {
  alert() {
    console.log('Car alert');
  }
}

一個(gè)類可以實(shí)現(xiàn)多個(gè)接口:

interface Alarm {
  alert(): void;
}

interface Light {
  lightOn(): void;
  lightOff(): void;
}

class Car implements Alarm, Light {
  alert() {
    console.log('Car alert');
  }
  lightOn() {
    console.log('Car light on');
  }
  lightOff() {
    console.log('Car light off');
  }
}

上例中证薇,Car 實(shí)現(xiàn)了 AlarmLight 接口度苔,既能報(bào)警,也能開關(guān)車燈浑度。

接口繼承接口

接口與接口之間可以是繼承關(guān)系:

interface Alarm {
  alert(): void;
}

interface LightableAlarm extends Alarm {
  lightOn(): void;
  lightOff(): void;
}

接口繼承類

接口也可以繼承類:

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

interface Point3d extends Point {
  z: number;
}

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

混合類型

我們知道寇窑,接口可以用來定義一個(gè)函數(shù):

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

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

有時(shí)候,一個(gè)函數(shù)還可以有自己的屬性和方法:

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

function getCounter(): Counter {
  const counter: Counter = start => start.toString();
  counter.interval = 123;
  counter.reset = () => { }
  return counter;
}

let c: Counter = getCounter();
c(10);
c.reset();
c.interval = 20;

泛型

泛型(Generics)是指在定義函數(shù)箩张、接口或類的時(shí)候甩骏,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性先慷。

  • T(Type):表示?個(gè) TypeScript 類型

  • K(Key):表示對(duì)象中的鍵類型

  • V(Value):表示對(duì)象中的值類型

  • E(Element):表示元素類型

簡單的例子

首先饮笛,我們來實(shí)現(xiàn)一個(gè)函數(shù) createArray,它可以創(chuàng)建一個(gè)指定長度的數(shù)組论熙,同時(shí)將每一項(xiàng)都填充一個(gè)默認(rèn)值:

type CreateArray = (length: number, value: any) => Array<any>;

let createArray: CreateArray = (length, value) => {
  let result = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

上例中福青,我們使用了數(shù)組泛型來定義返回值的類型。這段代碼不會(huì)報(bào)錯(cuò)脓诡,但是一個(gè)顯而易見的缺陷是炸庞,它并沒有準(zhǔn)確的定義返回值的類型:Array<any>允許數(shù)組的每一項(xiàng)都為任意類型。但是我們預(yù)期的是咸作,數(shù)組中每一項(xiàng)都應(yīng)該為 value 的類型篮条,這時(shí)候,泛型就派上用場了:

type CreateArray = <T>(length: number, value: T) => Array<T>;

// 箭頭函數(shù)
const createArray: CreateArray = <T>(length: number, value: T): Array<T> => {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

// 函數(shù)表達(dá)式
const createArray: CreateArray = function <T>(length: number, value: T) {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

// 聲明式函數(shù)
function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

createArray<number>(3, 1); // ['x', 'x', 'x']

在上例中踊跟,我們?cè)诤瘮?shù)中添加了 <T>踩验,其中 T 用來指代任意輸入的類型,在后面的輸入 value: T 和輸出 Array[T] 中即可使用了商玫。在調(diào)用的時(shí)候箕憾,指定他具體類型為 string, 當(dāng)然拳昌,也可以不手動(dòng)指定袭异,而讓類型推論自動(dòng)推算出來:

createArray(3, 1); // ['x', 'x', 'x']

多個(gè)類型參數(shù)

定義泛型的時(shí)候,可以次定義多個(gè)類型參數(shù):

type Swap = <T, U>(tuple: [T, U]) => [U, T];
const swap: Swap = <T, U>([p1, p2]: [T, U]): [U, T] => [p2, p1];
const result = swap([1, "2"]);

在上例中炬藤,我們定義了一個(gè) swap 函數(shù)御铃,用來交換輸入的 tuple碴里。

泛型約束

在函數(shù)內(nèi)部使用泛型變量的時(shí)候, 由于事先不知道它是哪種類型上真,所以不能隨意的操作它的屬性或方法:

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

// error TS2339: Property 'length' does not exist on type 'T'.

上例中咬腋,泛型 T 不一定包含屬性 length,所以編譯的時(shí)候報(bào)錯(cuò)了睡互。這時(shí)根竿,我們可以對(duì)泛型進(jìn)行約束,只允許這個(gè)函數(shù)傳入包含 length 屬性的變量就珠。這就是泛型約束:

interface ILengthwise {
  length: number;
}

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

我們使用 extends 約束了泛型 T 必須符合接口 ILengthwise 的定義寇壳,也就是必須包含 length 屬性。那么這時(shí)妻怎,如果調(diào)用 loggingIdentity 的時(shí)候壳炎,傳入的 arg 不包含 length,那么在編譯階段就會(huì)報(bào)錯(cuò)了:

loggingIdentity(7); // error TS2345: Argument of type '7' is not assignable to parameter of type 'ILengthwise'.
loggingIdentity('7'); // OK

多個(gè)類型參數(shù)之間也可以相互約束:

function copyFields<T extends U, U>(target: T, source: U): T {
  for (let key in source) {
    target[key] = (<T>source)[key];
  }
  return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });

上例中逼侦,我們使用了兩個(gè)類型參數(shù)匿辩,其中要求 T 繼承 U,這樣就保證了 U 上不會(huì)出現(xiàn) T 中不存在的字段偿洁。

泛型接口

我們可以使用接口的方式來定義一個(gè)函數(shù):

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

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

當(dāng)然也可以使用含有泛型的接口來定義函數(shù):

interface CreateArrayFunc {
  <T>(length: number, value: T): Array<T>;
}

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

進(jìn)一步撒汉,我們還可以把泛型參數(shù)提到接口名上:

interface CreateArrayFunc<T> {
  (length: number, value: T): Array<T>;
}

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

createArray(3, "x");

注意,此時(shí)在使用泛型接口的時(shí)候涕滋,需要定義泛型的類型睬辐。

泛型類

與泛型接口類似,泛型也可以用于類的類型定義中:

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

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

泛型參數(shù)的默認(rèn)類型

在 TypeScript 2.3 以后宾肺,我們可以為泛型中的類型參數(shù)指定默認(rèn)類型溯饵。當(dāng)使用泛型時(shí)沒有在代碼中直接指定類型參數(shù),從實(shí)際值參數(shù)中也無法推測(cè)出時(shí)锨用,這個(gè)默認(rèn)類型就會(huì)起作用丰刊。

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

聲明文件

我們?cè)陂_發(fā)過程中不可避免引入其它第三方的庫,雖然通過直接引用增拥,可以調(diào)用庫提供的方法啄巧,但是卻無法使用 Typescript 類型檢查等特性功能。為了解決這個(gè)問題掌栅,需要將這些庫里的函數(shù)去掉方法體秩仆,然后導(dǎo)出類型聲明,這樣就產(chǎn)生了一個(gè)描述庫和模塊信息的聲明文件猾封。通過引用這個(gè)聲明文件澄耍,就可以借用 TypeScript 的各種特性來使用庫文件了。

聲明語句

假如我們想使用第三方庫 jquery,一種常見的方式是在 html 中通過 script 標(biāo)簽引入 jquery齐莲,然后就可以使用全局變量 $jQuery了痢站。比如,我們通常這樣獲取一個(gè) idfoo 的元素:

$('#foo');
// or
jQuery('#foo'); //  error TS2581: Cannot find name '$'.

但是在 ts 中选酗,編譯器并不知道 $jQuery 是什么東西阵难。這時(shí),我們需要使用 declare var 來定義它的類型:

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

上例中星掰,declare var 并沒有真的定義一個(gè)變量多望,只是定義了全局變量 $jQuery 的類型,僅僅會(huì)用于編譯時(shí)的檢查氢烘,在編譯結(jié)果中會(huì)被刪除。他的編譯結(jié)果是:

$("#id");

除了 declare var 之外家厌,還有其他很多種聲明語句播玖,稍后介紹。

聲明文件

通常我們會(huì)把聲明語句放到一個(gè)單獨(dú)的文件 jQuery.d.ts 中饭于,這就是聲明文件:

// jQuery.d.ts
declare var jQuery: (selector: string) => any;

聲明文件必須以 .d.ts 為后綴蜀踏。一般來說,ts 會(huì)解析項(xiàng)目中所有的 *.ts 文件掰吕,當(dāng)然也包含以 .d.ts 結(jié)尾的文件果覆。所以,當(dāng)我們將 jQuery.d.ts 放到項(xiàng)目中時(shí)殖熟,其他所有的 *.ts 文件就都可以獲得 $ 的類型定義了局待。這里只演示了全局變量這種模式的聲明文件,假如是通過模塊導(dǎo)入的方式使用第三方庫的話菱属,那么引入聲明文件又是另一種方式了钳榨。

第三方聲明文件

當(dāng)然,jQuery 的聲明文件不需要我們定義了纽门,社區(qū)已經(jīng)幫我們定義好了:jquery.d.ts薛耻。我們可以下載下來直接使用,但是更推薦的是使用 @types 統(tǒng)一管理第三方庫的聲明文件赏陵。@types 的使用方式很簡單饼齿,直接用 npm 安裝對(duì)應(yīng)的聲明模塊即可,以 jQuery 為例:

npm install @types/jquery --save-dev

你可以在這個(gè)頁面搜索需要的聲明文件蝙搔,DefinitelyTyped缕溉。

自定義聲明文件

當(dāng)一個(gè)第三方庫沒有提供聲明文件時(shí),我們就需要手動(dòng)書寫聲明文件杂瘸。前面只介紹了最簡單的聲明文件內(nèi)容倒淫,而真正書寫一個(gè)聲明文件并不是一件簡單的事。以下會(huì)詳細(xì)介紹如何書寫聲明文件。在不同的場景下敌土,聲明文件的內(nèi)容和使用方式會(huì)有所區(qū)別镜硕。

庫的使用場景主要由以下幾種:

  • 全局變量:通過 <script> 標(biāo)簽引入第三方庫,注入全局變量返干。

  • npm 包:通過 import ** from xx 導(dǎo)入兴枯,符合 ES6 模塊化規(guī)范。

  • UMD 庫:即可以通過 <script> 標(biāo)簽引入矩欠,也可以通過 import 引入财剖。

  • 直接擴(kuò)展全局變量:通過 <script> 標(biāo)簽引入后,改變一個(gè)全局變量的結(jié)構(gòu)癌淮。String.prototype 新增了一個(gè)方法躺坟。

  • 通過導(dǎo)入擴(kuò)展全局變量:通過 import 導(dǎo)入后,可以改變一個(gè)全局變量的結(jié)構(gòu)乳蓄。

全局變量

全局變量是最簡單的一種場景咪橙,之前的示例中就是通過 <script> 標(biāo)簽引入 jQuery,注入全局變量 $jQuery虚倒。

使用全局變量的聲明文件時(shí)美侦,如果是以 npm install @types/xxx --save-dev 安裝的,則不需要任何配置魂奥。如果是將聲明文件直接存放到當(dāng)前項(xiàng)目中菠剩,則建議和其他源碼一起放到 src 目錄下(或者對(duì)應(yīng)的源碼目錄下):

├── README.md
├── src
|  ├── index.ts
|  └── jQuery.d.ts
└── tsconfig.json

全局變量的聲明文件主要由以下幾種語法:

  • declare var 聲明全局變量

  • declare function 聲明全局方法

  • declare class 聲明全局類

  • declare enum 聲明全局枚舉類型

  • declare namespace 聲明全局對(duì)象

  • interfacetype 聲明全局類型

declare var

在所有的的聲明語句中,declare var 是最簡單的耻煤,它可以用來定義一個(gè)全局變量的類型具壮。與其類似的是,還有 declare letdeclare const违霞,使用 let 與使用 var 沒什么區(qū)別嘴办,但是使用 const 定義時(shí),表示此時(shí)的全局變量是一個(gè)常量买鸽。一般來說涧郊,全局變量都是禁止修改的,所以大部分情況都應(yīng)該使用 const 而不是 varlet眼五。需要注意的是妆艘,在聲明語句中只能定義類型,切勿在聲明語句中定義具體的值看幼。

// 全局變量foo包含了存在組件總數(shù)批旺。
declare var foo: number;
declare function

declare function 用來定義全局函數(shù)的類型。jQuery 其實(shí)就是一個(gè)函數(shù)诵姜,所以也可以用 function 來定義:

declare function jQuery(selector: string): any;

在函數(shù)類型的聲明語句中汽煮,函數(shù)重載也是支持的:

declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
declare class

當(dāng)全局變量是一個(gè)類的時(shí)候,我們用 declare class 來定義它的類型:

declare class Animal {
  name: string;
  constructor(name: string);
  sayName(): void
}

同樣的,declare class 語句也只能用來定義類型暇赤,不能用來定義具體的值心例,比如定義 sayName 方法的具體實(shí)現(xiàn)則會(huì)報(bào)錯(cuò):

declare class Animal {
  name: string;
  constructor(name: string);
  sayName() { } // error TS1183: An implementation cannot be declared in ambient contexts.
}
declare enum

使用 declare enum 定義的枚舉類型也稱作外部枚舉,如:

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

其中鞋囊,Directions 是由第三方庫定義好的全局變量止后。

declare namespace

namespace 是 ts 早期為了解決模塊化而創(chuàng)造出來的關(guān)鍵字,中文稱為命名空間溜腐。由于歷史原因译株,在早期還沒有 ES6 的時(shí)候,ts 提供了一種模塊化方案挺益,使用 module 關(guān)鍵字表示內(nèi)部模塊歉糜。但由于后來 ES6 也使用了 module 關(guān)鍵字, ts 為了兼容 ES6矩肩,使用 namespace 替代了自己的 module现恼,更名為命名空間。隨著 ES6 的廣泛應(yīng)用黍檩,現(xiàn)在已經(jīng)不建議再使用 ts 中的 namespace,而推薦使用 ES6 的模塊化方案始锚,故我們不再需要學(xué)習(xí) namespace 的使用了刽酱。namespace 被淘汰了,但是在聲明文件中瞧捌,declare namespace 還是比較常用的棵里,它用來表示全局變量是一個(gè)對(duì)象,包含很多屬性姐呐。

比如殿怜,jQuery 是一個(gè)全局變量,它是一個(gè)對(duì)象曙砂,提供了一個(gè) jQuery.ajax 方法可以調(diào)用头谜,那么我們就應(yīng)該使用 declare namespace jQuery 來聲明這個(gè)擁有多個(gè)子屬性的全局變量。

declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
}

jQuery.ajax('/api/get_something');

declare namespace 內(nèi)部鸠澈,我們直接使用 function ajax 來聲明函數(shù)柱告,而不是使用 declare function ajax。類似的笑陈,也可以使用 const际度,classenum等語句涵妥。

declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
  const version: number;
  class Event {
    blur(eventType: EventType): void
  }
  enum EventType {
    CustomClick
  }
}

jQuery.ajax('/api/get_something');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick);

在編譯之后乖菱,declare namespace 內(nèi)的所有內(nèi)容都會(huì)被刪除。

嵌套的命名空間

如果對(duì)象擁有更深的層級(jí),則需要使用 namespace 來聲明深層的屬性類型:

declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
  namespace fn {
    function extend(object: any): void;
  }
}

jQuery.ajax('/api/get_something');
jQuery.fn.extend({
  check: function() {
      return this.each(() => {
          this.checked = true;
      });
  }
});

假如 jQuery 下僅有 fn 這一個(gè)屬性(沒有 ajax 等其他屬性或方法)窒所,則可以不需要嵌套 namespace

declare namespace jQuery.fn {
  function extend(object: any): void;
}

jQuery.fn.extend({
  check: function () {
    return this.each(() => {
      this.checked = true;
    });
  }
});
interface 和 type

除了全局變量之外鹉勒,有一些類型我們可能也希望暴露出來。在類型聲明文件中墩新,我們可以直接使用 interfacetype 來聲明一個(gè)全局的類型:

interface IAjaxSetting {
  method: "GET" | "POST";
  data?: any;
}

declare namespace jQuery {
  function ajax(url: string, settings?: IAjaxSetting): void;
}

這樣的話贸弥,在其他文件中也可以使用這個(gè)接口了:

const setting: IAjaxSetting = {
  method: "GET"
};

jQuery.ajax('/api/post_something', setting);

typeinterface 類似,不在贅述海渊。

防止命名沖突

暴露在最外層的 interfacetype 會(huì)作為全局類型作用于整個(gè)項(xiàng)目中绵疲,我們應(yīng)該盡可能的減少全局變量或全局類型的數(shù)量,因此臣疑,將他們放在 namespace 下:

declare namespace jQuery {
  interface IAjaxSetting {
    method: "GET" | "POST";
    data?: any;
  }
  function ajax(url: string, settings?: IAjaxSetting): void;
}

那么盔憨,在使用 IAjaxSetting 接口的時(shí)候,也應(yīng)該加上 jQuery 前綴了:

const setting: jQuery.IAjaxSetting = {
  method: "GET"
};
聲明合并

jQuery 即是一個(gè)函數(shù)讯沈,可以直接調(diào)用郁岩,也可以是一個(gè)對(duì)象,擁有子屬性缺狠,則我們可以組合多個(gè)聲明語句问慎,他們會(huì)不沖突的合并起來:

declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
declare namespace jQuery {
  interface IAjaxSetting {
    method: "GET" | "POST";
    data?: any;
  }
  function ajax(url: string, settings?: IAjaxSetting): void;
}

const setting: jQuery.IAjaxSetting = {
  method: "GET"
};
jQuery("#app");
jQuery(() => { });
jQuery.ajax('/api/post_something', setting);

npm 包

一般我們通過 import xxx from "xxx" 導(dǎo)入一個(gè) npm 包,這是符合 ES6 模塊規(guī)范的挤茄。當(dāng)我們嘗試給一個(gè) npm 包創(chuàng)建聲明文件之前如叼,首先看看它的聲明文件是否存在。一般來說穷劈,npm 包的聲明文件可能存在于兩個(gè)地方:

  1. 與該 npm 包綁定在一起笼恰。判斷依據(jù)是 package.json 中有 types 字段,或者有一個(gè) index.d.ts 聲明文件歇终。這種模式不需要額外安裝其他包社证,是最為推薦的,所以以后我們自己創(chuàng)建 npm 包的時(shí)候评凝,最好也將聲明文件與 npm 包綁定在一起追葡。

  2. 發(fā)布到了 @types 里只要嘗試安裝一下對(duì)應(yīng)的包就知道是否存在,安裝命令是 npm install @types/xxx --save-dev肥哎。這種模式一般是由于 npm 包的維護(hù)者沒有提供聲明文件辽俗,所以只能由其他人將聲明文件發(fā)布到 @types 里了。

假如以上兩種方式都沒有找到對(duì)應(yīng)的聲明文件篡诽,那么我們就需要自己為它寫聲明文件了崖飘。憂郁是通過 import 語句導(dǎo)入的模塊,所以聲明文件存放的位置也有所約束杈女,一般有兩種方案:

  1. 創(chuàng)建一個(gè) node_modules/@types/xxx/index.d.ts 文件朱浴,存放 xxx 模塊的聲明文件吊圾。這種方式不需要額外的配置,但是 node_modules 目錄不穩(wěn)定翰蠢,代碼也沒有被保存到倉庫中项乒,無法回溯版本,有不小心被刪除的風(fēng)險(xiǎn)梁沧。

  2. 創(chuàng)建一個(gè) types 目錄檀何,專門用來管理自己寫的聲明文件,將 xxx 的聲明文件放到 types/xxx/index.d.ts 中廷支。這種方式需要配置下 tsconfig.jsonpathsbaseUrl 字段频鉴。

目錄結(jié)構(gòu):

├── README.md
├── src
|  └── index.ts
├── types
|  └── xxx
|     └── index.d.ts
└── tsconfig.json

tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",         /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
        "baseUrl": "./",
        "paths": {
            "*" : ["types/*"]
        }
    }
}

如何配置之后,通過 import 導(dǎo)入 xxx 的時(shí)候恋拍,也會(huì)去 types 目錄下尋找對(duì)應(yīng)的模塊聲明文件了垛孔。

module 配置可以有很多種選項(xiàng),不同的選項(xiàng)會(huì)影響到導(dǎo)入導(dǎo)出模式施敢。

不管采用了以上兩種方式中的哪一種周荐,我都強(qiáng)烈建議大家將書寫好的聲明文件(通過給原作者發(fā) pr,或者直接提交到 @types 里)發(fā)布到開源社區(qū)中僵娃,享受了這么多社區(qū)的優(yōu)秀的資源概作,就應(yīng)該在力所能及的時(shí)候給出一些回饋。只有所有人都參與進(jìn)來默怨,才能讓 ts 社區(qū)更加繁榮仆嗦。

export

npm 包的聲明文件與全局變量的聲明文件有很大的區(qū)別。在 npm 包的聲明文件中先壕,使用 declare 不再會(huì)聲明一個(gè)全局變量,而只會(huì)在當(dāng)前文件中聲明一個(gè)局部變量谆甜。只有在聲明文件中使用 export 導(dǎo)出垃僚,然后在引入方 import 導(dǎo)入后,才會(huì)應(yīng)用到這些類型的聲明规辱。

export 的語法與非聲明文件中的語法類似谆棺,區(qū)別僅在于聲明文件中禁止定義具體的值:

export const name: string;
export function getName(): string;
export class Animal {
  constructor(name: string);
  sayHi(): string;
}
export enum Directions {
  Up,
  Down,
  Left,
  Right
}
export interface Options {
  data: any;
}

對(duì)應(yīng)的導(dǎo)入和使用模塊應(yīng)該是這樣:

import { name, getName, Animal, Directions, Options } from "foo";
console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
  data: {
    name: 'foo'
  }
}

混用 declare 和 export

我們也可以使用 declare 先聲明多個(gè)變量,最后再用 export 一次性導(dǎo)出罕袋。上例的聲明文件可以等價(jià)的改寫成:

declare const name: string;
declare function getName(): string;
declare class Animal {
  constructor(name: string);
  sayHi(): string;
}
declare enum Directions {
  Up,
  Down,
  Left,
  Right
}
interface Options {
  data: any;
}

export {
  name,
  getName,
  Animal,
  Directions,
  Options
}

與全局變量的聲明文件類似改淑,interface 前是不需要 declare 的。

export namespace

declare namespace 類似浴讯,export namespace 也是用來導(dǎo)出一個(gè)擁有子屬性的對(duì)象:

// types/foo/index.d.ts
export namespace foo {
  const name: string;
  namespace bar {
    function baz(): string;
  }
}

// src/main.ts
import { foo } from "./types/foo/index.d"
console.log(foo.name);
foo.bar.baz();

export default

在 ES6 模塊系統(tǒng)中朵夏,使用 export default 可以導(dǎo)出一個(gè)默認(rèn)值,引入方可以使用 import foo from "foo"榆纽,而不是使用 import { foo } from "foo" 來導(dǎo)入這個(gè)默認(rèn)值仰猖。在類型聲明文件中捏肢,export default 用來導(dǎo)出默認(rèn)值的類型:

// types/foo/index.d.ts
export default function foo(): string;

// src/index.ts
import foo from 'foo';
foo();

注意,只有 function饥侵、classinterface 可以直接默認(rèn)導(dǎo)出鸵赫,其他的變量需要先定義出來,再默認(rèn)導(dǎo)出:

export default enum Directions {  // error TS1109: Expression expected.
  Up,
  Down,
  Left,
  Right
}

上例中躏升,export default enum 是錯(cuò)誤的語法辩棒,需要先使用 declare enum 定義出來,再使用 export default 導(dǎo)出:

export default Directions;

declare enum Directions {  
  Up,
  Down,
  Left,
  Right
}

如上膨疏,針對(duì)這種默認(rèn)導(dǎo)出若债,我們一般會(huì)將導(dǎo)出語句房子啊整個(gè)聲明文件的最前面。

作者-張然

日期:2021-7-3

TypeScript小結(jié)

快速搭建TypeScript開發(fā)環(huán)境

首先当娱,全局安裝TypeScript浓若。

npm install typescript -g

安裝完成后可以通過tsc -v檢查版本双霍。

創(chuàng)建demo項(xiàng)目砚偶,npm init -ytsc --init命令來初始化項(xiàng)目目錄,初始化好后會(huì)生成package.jsontsconfifig.json這兩個(gè)文件洒闸。

package.json是npm項(xiàng)目包管理文件染坯,tsconfifig.jsontypescript的配置文件。

新建index.ts文件丘逸,然后使用tsc index.ts命令來編譯单鹿,會(huì)在當(dāng)前文件下新建一個(gè)index.js文件。(-w啟動(dòng)熱編譯)

基本數(shù)據(jù)類型

TypeScript 支持與 JavaScript 幾乎相同的數(shù)據(jù)類型深纲,此外還提供了實(shí)用的枚舉類型方便我們使用仲锄。

boolean

let isDone: boolean = false;

number

JavaScript 一樣,TypeScript 里的所有數(shù)字都是浮點(diǎn)數(shù)湃鹊。 這些浮點(diǎn)數(shù)的類型是 number儒喊。 除了支持十進(jìn)制和十六進(jìn)制字面量,TypeScript 還支持 ECMAScript 2015 中引入的二進(jìn)制和八進(jìn)制字面量币呵。

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

string

JavaScript 一樣怀愧,可以使用雙引號(hào)( ")或單引號(hào)(')表示字符串。

let name: string = "bob";
name = "smith";

同樣也可以使用 字符串模板

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.

I'll be ${ age + 1 } years old next month.`;

array

有兩種方式定義數(shù)組余赢,第一種芯义,在數(shù)組元素類型后面使用 []

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

第二種,使用數(shù)組泛型妻柒,Array<元素類型>

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

Tuple

Tuple 類型也是一個(gè)數(shù)組扛拨,我們可以用它來表示一個(gè)已知元素?cái)?shù)量和元素類型的數(shù)組。 比如蛤奢,你可以定義一對(duì)值分別為 stringnumber類型的元組鬼癣。

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error

當(dāng)訪問一個(gè)已知索引的元素陶贼,會(huì)得到正確的類型:

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'

使用索引進(jìn)行越界訪問:

x[3] = 'world'; // Error, Tuple type '[string, number]' of length '2' has no element at index '2'.

調(diào)用數(shù)組的方法:

x.push("world"); // OK
x.push(true); // Error, Argument of type 'true' is not assignable to parameter of type 'string | number'.

1待秃、使用索引來訪問越界元素拜秧,編譯器會(huì)報(bào)錯(cuò)誤

2、使用 push 方法新增元素章郁,元素的類型必須滿足其聯(lián)合類型

enum

enum 類型是對(duì) javascript 標(biāo)準(zhǔn)數(shù)據(jù)類型的一個(gè)補(bǔ)充枉氮。

enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

默認(rèn)情況下,枚舉成員從 0 開始賦值暖庄,每次遞增步長為 1聊替,同時(shí),可以從值到名進(jìn)行反向映射:

// key -> value
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

// value -> key
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

同時(shí)培廓,我們也可以對(duì)枚舉項(xiàng)進(jìn)行手動(dòng)賦值惹悄,當(dāng)值為 number 類型時(shí),未賦值的枚舉項(xiàng)會(huì)接著上一個(gè)枚舉項(xiàng)依次賦值肩钠。

enum Days { Sun = 2, Mon, Tue = 5, Wed, Thu, Fri, Sat };

console.log(Days.Sun);  // 2
console.log(Days.Mon);  // 3
console.log(Days.Tue);  // 5
console.log(Days.Wed);  // 6
console.log(Days.Thu);  // 7

如果枚舉項(xiàng)的值有重復(fù)的話泣港,typescript 不會(huì)提示錯(cuò)誤,但是通過 value 獲取 key 的話价匠,key 是最后一次的枚舉項(xiàng):

enum Days { Sun = 2, Mon = 2, Tue = 1, Wed, Thu, Fri, Sat };
console.log(Days[2]); // Wed

在使用的時(shí)候当纱,最好不要出現(xiàn)覆蓋的情況。

手動(dòng)賦值的枚舉項(xiàng)可以不是 number 類型踩窖,但是坡氯,緊跟著的枚舉項(xiàng)必須給初始值,否則會(huì)報(bào)錯(cuò)洋腮。

enum Days { Sun = "s", Mon = 2, Tue = 1, Wed, Thu, Fri, Sat };

any

any 表示可以賦值為任意類型箫柳。

let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

針對(duì)未聲明類型的變量,它會(huì)被識(shí)別為 any

let something;
something = 'seven';
something = 7;

unknown

就像所有類型都可以賦值給 any 啥供,所有類型也都可以賦值給 unknown 滞时。使得 unknown 成為TypeScript 類型系統(tǒng)的另?種頂級(jí)類型(另?種是 any )。unknown 類型只能被賦值給 any 類型和 unknown 類型本身滤灯。

let value: unknown;
value = true;
value = 123;
value = [];
//...

let value0: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
//...

void

某種程度上來說,void類型像是與any類型相反曼玩,它表示沒有任何類型鳞骤。當(dāng)一個(gè)函數(shù)沒有返回值時(shí),你通常會(huì)見到其返回值類型是 void

function bar(): void {}

null和undefined類型

let u: undefined = undefined;
let n: null = null;

Never

表示的類型是那些總是會(huì)拋出異呈蚺校或根本就不會(huì)有返回值的函數(shù)表達(dá)式或箭頭函數(shù)表達(dá)式的返回值類型豫尽。

// 返回never的函數(shù)必須存在?法達(dá)到的終點(diǎn)
function error(message: string): never {
    throw new Error(message);
}
function infiniteLoop(): never {
    while (true) { }
}

類型推論

如果沒有明確的指定類型,那么 TypeScript 會(huì)依照類型推論(Type Inference)的規(guī)則推斷出一個(gè)類型顷帖。

什么是類型推論

以下代碼雖然沒有指定類型美旧,但是會(huì)在編譯的時(shí)候報(bào)錯(cuò):

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; // error TS2322: Type '7' is not assignable to type 'string'.

事實(shí)上渤滞,它等價(jià)于:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

TypeScript 會(huì)在沒有明確的指定類型的時(shí)候推測(cè)出一個(gè)類型榴嗅,這就是類型推論妄呕。

如果定義的時(shí)候沒有賦值,不管之后有沒有賦值嗽测,都會(huì)被推斷成 any 類型而完全不被類型檢查

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

聯(lián)合類型

聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種绪励。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

聯(lián)合類型使用 | 分隔每個(gè)類型。

訪問聯(lián)合類型的屬性和方法

當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候唠粥,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法

function getLength(something: string | number): number {
  return something.length;
}
// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

上例中疏魏,length 不是 stringnumber 的共有屬性,所以編譯器報(bào)錯(cuò)晤愧。

訪問 stringnumber 的共有屬性是沒問題的:

function getString(something: string | number): string {
  return something.toString();
}

聯(lián)合類型的變量在被賦值的時(shí)候大莫,會(huì)根據(jù)類型推論的規(guī)則推斷出一個(gè)類型:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length);
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // error TS2339: Property 'length' does not exist on type 'number'.

在上例中,第 2 行 myFavoriteNumber 被推斷成 string 類型官份,因此訪問其 length 屬性不會(huì)報(bào)錯(cuò)只厘。而第 4 行被推斷成 number,訪問 length 就報(bào)錯(cuò)了贯吓。

類型斷言

類型斷言(Type Assertion)可以用來手動(dòng)指定一個(gè)值的類型懈凹。

語法

<type> value 

// or

value as type

tsx 中必須使用后面一種。

前面在聯(lián)合類型中我們提到過悄谐,當(dāng) Typescript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候介评,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法

function getLength(something: string | number): number {
  return something.length;
}

// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

而有時(shí)候,我們確實(shí)需要在還不確定類型的時(shí)候就訪問其中一個(gè)類型的屬性或方法爬舰,比如:

function getLength(something: string | number): number {
  if (something.length) {
    return something.length;
  } else {
    return something.toString().length;
  }
}

// error TS2339: Property 'length' does not exist on type 'string | number'. Property 'length' does not exist on type 'number'.

在上例中们陆,訪問 something.length 的時(shí)候會(huì)報(bào)錯(cuò),因?yàn)?length 并不是公共屬性情屹。此時(shí)坪仇,我們就可以使用類型斷言,將 something 斷言成 string

function getLength(something: string | number): number {
  if ((<string>something).length) {
    return (something as string).length;
  } else {
    return something.toString().length;
  }
}

類型斷言不是類型轉(zhuǎn)換垃你,斷言成一個(gè)聯(lián)合類型中不存在的類型是不允許的

function toBoolean(something: string | number): boolean {
  return <boolean>something;
}

// error TS2352: Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type 'number' is not comparable to type 'boolean'.

類型別名

類型別名用來給一個(gè)類型起個(gè)新名字椅文,常用語聯(lián)合類型。

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

字符串字面量類型

字符串字面量類型用來約束取值只能是某幾個(gè)字符串中的一個(gè)惜颇。

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element | null , event: EventNames) {
  // do something
}

handleEvent(document.querySelector('hello'), 'scroll');
handleEvent(document.querySelector('world'), 'dbclick'); // error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

上例中皆刺,我們使用 type 定了一個(gè)字符串字面量類型 EventNames,它只能取三種字符串中的一種凌摄。

類型別名與字符串字面量類型都是使用 type 進(jìn)行定義羡蛾。

函數(shù)

聲明式函數(shù)

一個(gè)函數(shù)有輸入和輸出,要在 TypeScript 中對(duì)其進(jìn)行約束锨亏,需要把輸入和輸出都考慮到痴怨,其中函數(shù)聲明的類型定義較簡單:

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

輸入多余的(或者少于要求的)參數(shù)忙干,都是不被允許的。

sum(1, 2, 3); // error TS2554: Expected 2 arguments, but got 3.
sum(1); //Expected 2 arguments, but got 1.

函數(shù)表達(dá)式

如果要我們現(xiàn)在寫一個(gè)對(duì)函數(shù)表達(dá)式(Function Expression)的定義浪藻,可能會(huì)寫成這樣:

const sum = (x: number, y: number): number => x + y;

這是可以通過編譯的捐迫,不過事實(shí)上,上面的代碼只對(duì)等號(hào)右側(cè)的匿名函數(shù)進(jìn)行類型定義珠移,而等號(hào)左邊的 sum弓乙,是通過賦值操作進(jìn)行 類型推論 推斷出來的。如果我們需要手動(dòng)給 sum 添加類型钧惧,則應(yīng)該是這樣:

const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;

不要混淆了 TypeScript 中的 =>ES6 中的 =>暇韧。

TypeScript 的類型定義中,=> 用來表示函數(shù)的定義浓瞪,左邊是輸入類型懈玻,需要用括號(hào)括起來,右邊是輸出類型乾颁。

使用接口定義函數(shù)類型

我們可以通過接口來定義函數(shù)的類型:

interface ISum {
  (x: number, y: number): number
}

const sum: ISum = (x, y) => x + y;

可選參數(shù)

前面提到,輸入多余的(或者少于要求的)參數(shù)尖殃,是不允許的。那么如何定義可選的參數(shù)呢科乎?

與接口中的可選屬性類似令杈,我們用 ? 表示可選的參數(shù):

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}
let tomcat: string = buildName('Tom', 'Cat');
let tom: string = buildName('Tom');

需要注意的是纲刀,可選參數(shù)必須接在確定參數(shù)后面。換句話說摄杂,可選參數(shù)后面不允許再出現(xiàn)確定參數(shù)

function buildName(firstName?: string, lastName: string) {
  if (firstName) {
    return firstName + ' ' + lastName;
  } else {
    return lastName;
  }
}
// error TS1016: A required parameter cannot follow an optional parameter.

參數(shù)默認(rèn)值

ES6 中鞍时,我們?cè)试S給函數(shù)的參數(shù)添加默認(rèn)值灵再,TypeScript 會(huì)將添加了默認(rèn)值的參數(shù)識(shí)別為可選參數(shù)

function buildName(firstName: string, lastName: string = 'Cat') {
  return firstName + ' ' + lastName;
}

此時(shí)就不受「可選參數(shù)必須接在必需參數(shù)后面」的限制了:

function buildName(firstName: string = 'Tom', lastName: string) {
  return firstName + ' ' + lastName;
}

剩余參數(shù)

ES6 中,可以使用 ...rest 的方式獲取函數(shù)中的剩余參數(shù)(rest 參數(shù))

function push(array, ...items) {
  items.forEach(function (item) {
    array.push(item);
  });
}

事實(shí)上,items 是一個(gè)數(shù)組替久,所以我們可以用數(shù)組的類型來定義:

function push<A, B>(array: A[], ...items: B[]): void {
  items.forEach(item => {
    console.log(item);
  })
}

重載

重載允許一個(gè)函數(shù)接收不同數(shù)量或類型的參數(shù)時(shí)距帅,作出不同的處理。

比如抗碰,我們需要實(shí)現(xiàn)一個(gè)函數(shù) reverse去枷,輸入數(shù)字 123 時(shí)猎荠,返回反轉(zhuǎn)的數(shù)字 321蚕钦,輸入字符串 hello 時(shí)菠齿,返回反轉(zhuǎn)的字符串 olleh佑吝,利用聯(lián)合類型,我們可以這樣實(shí)現(xiàn):

type Reverse = string | number;

function reverse(x: Reverse): Reverse {
  if (typeof x === "number") {
    return Number(x.toString().split('').reverse().join(''));
  } else {
    return x.split('').reverse().join('');
  }
}

然而這樣做有一個(gè)缺點(diǎn)绳匀,就是不能 精確 的表達(dá)芋忿,輸入數(shù)字的時(shí)候,返回也是數(shù)字疾棵,輸入字符串的時(shí)候戈钢,也應(yīng)該返回字符串。這時(shí)陋桂,我們可以使用重載定義多個(gè) reverse 函數(shù)類型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string) {
  if (typeof x === "number") {
    return Number(x.toString().split('').reverse().join(''));
  } else {
    return x.split('').reverse().join('');
  }
}

以上代碼逆趣,我們重復(fù)多次定義了 reverse 函數(shù),前幾次都是函數(shù)的定義嗜历,最后一次是函數(shù)的實(shí)現(xiàn)宣渗,這時(shí),在編譯階段的提示中梨州,就可以正確的看到前兩個(gè)提示了痕囱。

TypeScript 會(huì)優(yōu)先從最前面的函數(shù)定義開始匹配,所以多個(gè)函數(shù)定義如果有包含關(guān)系暴匠,需要優(yōu)先把精確的定義寫在前面鞍恢。

接口

typescript 中,我們可以使用 interface 來定義復(fù)雜數(shù)據(jù)類型每窖,用來描述形狀或抽象行為帮掉。如:

interface IPerson {
  name: string;
  age: number;
  sayName(): void;
}

const p: IPerson = {
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

接口名稱首字母大寫,同時(shí)加上 I 前綴窒典。

變量 p 的類型是 IPerson蟆炊,這樣就約束了它的數(shù)據(jù)結(jié)構(gòu)必須和 IPerson 保持一致,多定義和少定義都是不被允許的瀑志。

賦值的時(shí)候涩搓,變量的形狀必須和接口的形狀保持一致污秆。

可選屬性

有時(shí),我們希望不要完全匹配接口中的屬性昧甘,那么可以用可選屬性:

interface IPerson {
  name: string;
  age: number;
  gender?: string; // 可選屬性
  sayName(): void;
}

const p: IPerson = {
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

在進(jìn)行賦值時(shí)良拼, gender 屬性是可以不存在的。當(dāng)然充边,這時(shí)仍然不允許添加接口中未定義的屬性庸推。

只讀屬性

有時(shí)候我們希望對(duì)象中的一些屬性只能在創(chuàng)建的時(shí)候被賦值,那么可以用 readonly 定義只讀屬性:

interface IPerson {
  readonly id: number;        // 只讀屬性
  name: string;
  age: number;
  gender?: string;
  sayName(): void;
}

只讀約束存在于第一次給對(duì)象賦值的時(shí)候浇冰,而不是第一次給只讀屬性賦值的時(shí)候予弧。 因此,在對(duì)象初始化的時(shí)候湖饱,必須賦值,之后杀捻,這個(gè)屬性就不能再賦值井厌。

const p: IPerson = {
  id: 1,
  name: "tom",
  age: 21,
  sayName() {
    console.log(this.name);
  }
};

const vs readonly:變量用 const,對(duì)象屬性用 readonly

任意屬性

有時(shí)候致讥,我們希望一個(gè)接口允許有任意屬性:

interface IPerson {
  readonly id: number;
  name: string;
  age: number;
  gender?: string;
  sayName(): void;
  [propsName: string]: any; // 任意屬性
}

[propsName: string]: any;通過 字符串索引簽名 的方式仅仆,我們就可以給 IPerson 類型的變量上賦值任意數(shù)量的其他類型。

const p: IPerson = {
  id: 1,
  name: "tom",
  age: 21,
  email: "102376640@qq.com", // 任意屬性
  phone: 1234567890, // 任意屬性
  sayName() {
    console.log(this.name);
  },
};

emailphone 屬性沒有在 IPerson 中顯性定義垢袱,但是編譯器不會(huì)報(bào)錯(cuò)墓拜,這是因?yàn)槲覀兌x了字符串索引簽名。

一旦定義字符串索引簽名请契,那么接口中的確定屬性和可選屬性的類型必須是索引簽名類型的子集咳榜。

interface IPerson {
    name: string;
    age?: number;
    [propName: string]: string;
}

// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.ts(2411)
// (property) IPerson.age?: number | undefined

[propName: string]: string;字符串索引簽名類型為 string,但是可選屬性 agenumber 類型爽锥,number 并不是 string 的子集涌韩, 因此編譯報(bào)錯(cuò)。

表示數(shù)組

接口除了可以用來描述對(duì)象以外氯夷,還可以用來描述數(shù)組類型臣樱,也就是數(shù)字索引簽名:

interface NumberArray {
  [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

變量 fibonacci 的類型是 NumberArray,如果還想調(diào)用數(shù)組的方法腮考,則:

interface NumberArray<T> extends Array<T> {
  [index: number]: T;
}
let fibonacci: NumberArray<number> = [1, 1, 2, 3, 5];

表示函數(shù)

接口還可以用來描述函數(shù)雇毫,約束參數(shù)的個(gè)數(shù),類型以及返回值:

interface ISearchFunc {
  (source: string, subString: string): boolean
}

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

修飾符

TypeScript 可以使用三種訪問修飾符(Access Modifiers)踩蔚,分別是 public棚放、privateprotected

  • public 修飾的屬性或方法是公有的寂纪,可以在任何地方被訪問到席吴,默認(rèn)所有的屬性和方法都是 public****的

  • private 修飾的屬性或方法是私有的赌结,不能在聲明它的類的外部訪問

  • protected 修飾的屬性或方法是受保護(hù)的,它和 private 類似孝冒,區(qū)別是它在子類中也是允許被訪問的

例子:

class Animal {
  public name: string;
  public constructor(name: string) {
    this.name = name;
  }
}

let a = new Animal('Jack');
console.log(a.name);
a.name = 'Tom';
console.log(a.name);

上面的例子中柬姚,name 被設(shè)置為 public,所以直接訪問實(shí)例的 name 屬性是允許的庄涡。如果希望 name不被外部訪問量承,這時(shí)候就可以用 private

class Animal {
  private name: string;
  public constructor(name: string) {
    this.name = name;
  }
}

let a = new Animal('Jack');
console.log(a.name); // error TS2341: Property 'name' is private and only accessible within class 'Animal'.
a.name = 'Tom'; // error TS2341: Property 'name' is private and only accessible within class 'Animal'.
console.log(a.name); // error TS2341: Property 'name' is private and only accessible within class 'Animal'.

使用 private 修飾的屬性或方法,在子類中也是不允許訪問的:

class Cat extends Animal {
  constructor(name: string) {
    super(name);
    console.log(this.name); // error TS2341: Property 'name' is private and only accessible within class 'Animal'.
  }
}

如果使用 protected 修飾穴店,則允許在子類中訪問:

class Animal {
  protected name: string;
  public constructor(name: string) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
    console.log(this.name);
  }
}

抽象類

abstract 用于定義抽象類和其中的抽象方法撕捍,抽象類是不允許被實(shí)例化的:

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract sayHello(): void;
  sayName() {
    console.log(this.name);
  }
}

new Animal("Jack"); // error TS2511: Cannot create an instance of an abstract class.

其次,抽象類中的抽象方法泣洞,必須被子類實(shí)現(xiàn):

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  abstract sayHello(): void;
  sayName() {
    console.log(this.name);
  }
}

class Cat extends Animal {
  sayHello(): void {
    console.log("hello");
  }
}

const cat: Cat = new Cat("Tom");
cat.sayName();        // ok
cat.sayHello();        // ok

類與接口

實(shí)現(xiàn)接口

實(shí)現(xiàn)(implements)是面向?qū)ο笾械囊粋€(gè)重要概念忧风。一般來講,一個(gè)類只能繼承自另一個(gè)類球凰,有時(shí)候不同類之間可以有一些共有的特性狮腿,這時(shí)候就可以把特性提取成接口(interfaces),用 implements 關(guān)鍵字來實(shí)現(xiàn)呕诉。這個(gè)特性大大提高了面向?qū)ο蟮撵`活性缘厢。

舉例來說,門是一個(gè)類甩挫,防盜門是門的子類贴硫。如果防盜門有一個(gè)報(bào)警器的功能,我們可以簡單的給防盜門添加一個(gè)報(bào)警方法伊者。這時(shí)候如果有另一個(gè)類英遭,車,也有報(bào)警器的功能亦渗,就可以考慮把報(bào)警器提取出來贪绘,作為一個(gè)接口,防盜門和車都去實(shí)現(xiàn)它:

interface Alarm {
  alert(): void;
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
  alert() {
    console.log('SecurityDoor alert');
  }
}

class Car implements Alarm {
  alert() {
    console.log('Car alert');
  }
}

一個(gè)類可以實(shí)現(xiàn)多個(gè)接口:

interface Alarm {
  alert(): void;
}

interface Light {
  lightOn(): void;
  lightOff(): void;
}

class Car implements Alarm, Light {
  alert() {
    console.log('Car alert');
  }
  lightOn() {
    console.log('Car light on');
  }
  lightOff() {
    console.log('Car light off');
  }
}

上例中央碟,Car 實(shí)現(xiàn)了 AlarmLight 接口税灌,既能報(bào)警,也能開關(guān)車燈亿虽。

接口繼承接口

接口與接口之間可以是繼承關(guān)系:

interface Alarm {
  alert(): void;
}

interface LightableAlarm extends Alarm {
  lightOn(): void;
  lightOff(): void;
}

接口繼承類

接口也可以繼承類:

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

interface Point3d extends Point {
  z: number;
}

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

混合類型

我們知道菱涤,接口可以用來定義一個(gè)函數(shù):

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

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

有時(shí)候,一個(gè)函數(shù)還可以有自己的屬性和方法:

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

function getCounter(): Counter {
  const counter: Counter = start => start.toString();
  counter.interval = 123;
  counter.reset = () => { }
  return counter;
}

let c: Counter = getCounter();
c(10);
c.reset();
c.interval = 20;

泛型

泛型(Generics)是指在定義函數(shù)洛勉、接口或類的時(shí)候粘秆,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性收毫。

  • T(Type):表示?個(gè) TypeScript 類型

  • K(Key):表示對(duì)象中的鍵類型

  • V(Value):表示對(duì)象中的值類型

  • E(Element):表示元素類型

簡單的例子

首先攻走,我們來實(shí)現(xiàn)一個(gè)函數(shù) createArray殷勘,它可以創(chuàng)建一個(gè)指定長度的數(shù)組,同時(shí)將每一項(xiàng)都填充一個(gè)默認(rèn)值:

type CreateArray = (length: number, value: any) => Array<any>;

let createArray: CreateArray = (length, value) => {
  let result = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

上例中昔搂,我們使用了數(shù)組泛型來定義返回值的類型玲销。這段代碼不會(huì)報(bào)錯(cuò),但是一個(gè)顯而易見的缺陷是摘符,它并沒有準(zhǔn)確的定義返回值的類型:Array<any>允許數(shù)組的每一項(xiàng)都為任意類型贤斜。但是我們預(yù)期的是,數(shù)組中每一項(xiàng)都應(yīng)該為 value 的類型逛裤,這時(shí)候瘩绒,泛型就派上用場了:

type CreateArray = <T>(length: number, value: T) => Array<T>;

// 箭頭函數(shù)
const createArray: CreateArray = <T>(length: number, value: T): Array<T> => {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

// 函數(shù)表達(dá)式
const createArray: CreateArray = function <T>(length: number, value: T) {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

// 聲明式函數(shù)
function createArray<T>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

createArray<number>(3, 1); // ['x', 'x', 'x']

在上例中,我們?cè)诤瘮?shù)中添加了 <T>带族,其中 T 用來指代任意輸入的類型锁荔,在后面的輸入 value: T 和輸出 Array[T] 中即可使用了。在調(diào)用的時(shí)候蝙砌,指定他具體類型為 string堕战, 當(dāng)然,也可以不手動(dòng)指定拍霜,而讓類型推論自動(dòng)推算出來:

createArray(3, 1); // ['x', 'x', 'x']

多個(gè)類型參數(shù)

定義泛型的時(shí)候,可以次定義多個(gè)類型參數(shù):

type Swap = <T, U>(tuple: [T, U]) => [U, T];
const swap: Swap = <T, U>([p1, p2]: [T, U]): [U, T] => [p2, p1];
const result = swap([1, "2"]);

在上例中薪介,我們定義了一個(gè) swap 函數(shù)祠饺,用來交換輸入的 tuple

泛型約束

在函數(shù)內(nèi)部使用泛型變量的時(shí)候汁政, 由于事先不知道它是哪種類型道偷,所以不能隨意的操作它的屬性或方法:

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

// error TS2339: Property 'length' does not exist on type 'T'.

上例中,泛型 T 不一定包含屬性 length记劈,所以編譯的時(shí)候報(bào)錯(cuò)了勺鸦。這時(shí),我們可以對(duì)泛型進(jìn)行約束目木,只允許這個(gè)函數(shù)傳入包含 length 屬性的變量换途。這就是泛型約束:

interface ILengthwise {
  length: number;
}

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

我們使用 extends 約束了泛型 T 必須符合接口 ILengthwise 的定義,也就是必須包含 length 屬性刽射。那么這時(shí)军拟,如果調(diào)用 loggingIdentity 的時(shí)候中剩,傳入的 arg 不包含 length邑蒋,那么在編譯階段就會(huì)報(bào)錯(cuò)了:

loggingIdentity(7); // error TS2345: Argument of type '7' is not assignable to parameter of type 'ILengthwise'.
loggingIdentity('7'); // OK

多個(gè)類型參數(shù)之間也可以相互約束:

function copyFields<T extends U, U>(target: T, source: U): T {
  for (let key in source) {
    target[key] = (<T>source)[key];
  }
  return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });

上例中钦无,我們使用了兩個(gè)類型參數(shù)嫉沽,其中要求 T 繼承 U眠寿,這樣就保證了 U 上不會(huì)出現(xiàn) T 中不存在的字段昙篙。

泛型接口

我們可以使用接口的方式來定義一個(gè)函數(shù):

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

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

當(dāng)然也可以使用含有泛型的接口來定義函數(shù):

interface CreateArrayFunc {
  <T>(length: number, value: T): Array<T>;
}

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

進(jìn)一步腔召,我們還可以把泛型參數(shù)提到接口名上:

interface CreateArrayFunc<T> {
  (length: number, value: T): Array<T>;
}

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

createArray(3, "x");

注意锌半,此時(shí)在使用泛型接口的時(shí)候,需要定義泛型的類型姑宽。

泛型類

與泛型接口類似遣耍,泛型也可以用于類的類型定義中:

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

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

泛型參數(shù)的默認(rèn)類型

在 TypeScript 2.3 以后,我們可以為泛型中的類型參數(shù)指定默認(rèn)類型低千。當(dāng)使用泛型時(shí)沒有在代碼中直接指定類型參數(shù)配阵,從實(shí)際值參數(shù)中也無法推測(cè)出時(shí),這個(gè)默認(rèn)類型就會(huì)起作用示血。

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

聲明文件

我們?cè)陂_發(fā)過程中不可避免引入其它第三方的庫棋傍,雖然通過直接引用,可以調(diào)用庫提供的方法难审,但是卻無法使用 Typescript 類型檢查等特性功能瘫拣。為了解決這個(gè)問題,需要將這些庫里的函數(shù)去掉方法體告喊,然后導(dǎo)出類型聲明麸拄,這樣就產(chǎn)生了一個(gè)描述庫和模塊信息的聲明文件。通過引用這個(gè)聲明文件黔姜,就可以借用 TypeScript 的各種特性來使用庫文件了拢切。

聲明語句

假如我們想使用第三方庫 jquery,一種常見的方式是在 html 中通過 script 標(biāo)簽引入 jquery秆吵,然后就可以使用全局變量 $jQuery了淮椰。比如,我們通常這樣獲取一個(gè) idfoo 的元素:

$('#foo');
// or
jQuery('#foo'); //  error TS2581: Cannot find name '$'.

但是在 ts 中纳寂,編譯器并不知道 $jQuery 是什么東西主穗。這時(shí),我們需要使用 declare var 來定義它的類型:

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

上例中毙芜,declare var 并沒有真的定義一個(gè)變量忽媒,只是定義了全局變量 $jQuery 的類型,僅僅會(huì)用于編譯時(shí)的檢查腋粥,在編譯結(jié)果中會(huì)被刪除晦雨。他的編譯結(jié)果是:

$("#id");

除了 declare var 之外,還有其他很多種聲明語句隘冲,稍后介紹金赦。

聲明文件

通常我們會(huì)把聲明語句放到一個(gè)單獨(dú)的文件 jQuery.d.ts 中,這就是聲明文件:

// jQuery.d.ts
declare var jQuery: (selector: string) => any;

聲明文件必須以 .d.ts 為后綴对嚼。一般來說夹抗,ts 會(huì)解析項(xiàng)目中所有的 *.ts 文件,當(dāng)然也包含以 .d.ts 結(jié)尾的文件纵竖。所以漠烧,當(dāng)我們將 jQuery.d.ts 放到項(xiàng)目中時(shí)杏愤,其他所有的 *.ts 文件就都可以獲得 $ 的類型定義了。這里只演示了全局變量這種模式的聲明文件已脓,假如是通過模塊導(dǎo)入的方式使用第三方庫的話珊楼,那么引入聲明文件又是另一種方式了。

第三方聲明文件

當(dāng)然度液,jQuery 的聲明文件不需要我們定義了厕宗,社區(qū)已經(jīng)幫我們定義好了:jquery.d.ts。我們可以下載下來直接使用堕担,但是更推薦的是使用 @types 統(tǒng)一管理第三方庫的聲明文件已慢。@types 的使用方式很簡單,直接用 npm 安裝對(duì)應(yīng)的聲明模塊即可霹购,以 jQuery 為例:

npm install @types/jquery --save-dev

你可以在這個(gè)頁面搜索需要的聲明文件佑惠,DefinitelyTyped

自定義聲明文件

當(dāng)一個(gè)第三方庫沒有提供聲明文件時(shí)齐疙,我們就需要手動(dòng)書寫聲明文件膜楷。前面只介紹了最簡單的聲明文件內(nèi)容,而真正書寫一個(gè)聲明文件并不是一件簡單的事贞奋。以下會(huì)詳細(xì)介紹如何書寫聲明文件赌厅。在不同的場景下,聲明文件的內(nèi)容和使用方式會(huì)有所區(qū)別轿塔。

庫的使用場景主要由以下幾種:

  • 全局變量:通過 <script> 標(biāo)簽引入第三方庫特愿,注入全局變量。

  • npm 包:通過 import ** from xx 導(dǎo)入催训,符合 ES6 模塊化規(guī)范。

  • UMD 庫:即可以通過 <script> 標(biāo)簽引入宗收,也可以通過 import 引入漫拭。

  • 直接擴(kuò)展全局變量:通過 <script> 標(biāo)簽引入后,改變一個(gè)全局變量的結(jié)構(gòu)混稽。String.prototype 新增了一個(gè)方法采驻。

  • 通過導(dǎo)入擴(kuò)展全局變量:通過 import 導(dǎo)入后,可以改變一個(gè)全局變量的結(jié)構(gòu)匈勋。

全局變量

全局變量是最簡單的一種場景礼旅,之前的示例中就是通過 <script> 標(biāo)簽引入 jQuery,注入全局變量 $jQuery洽洁。

使用全局變量的聲明文件時(shí)痘系,如果是以 npm install @types/xxx --save-dev 安裝的,則不需要任何配置饿自。如果是將聲明文件直接存放到當(dāng)前項(xiàng)目中汰翠,則建議和其他源碼一起放到 src 目錄下(或者對(duì)應(yīng)的源碼目錄下):

├── README.md
├── src
|  ├── index.ts
|  └── jQuery.d.ts
└── tsconfig.json

全局變量的聲明文件主要由以下幾種語法:

  • declare var 聲明全局變量

  • declare function 聲明全局方法

  • declare class 聲明全局類

  • declare enum 聲明全局枚舉類型

  • declare namespace 聲明全局對(duì)象

  • interfacetype 聲明全局類型

declare var

在所有的的聲明語句中龄坪,declare var 是最簡單的,它可以用來定義一個(gè)全局變量的類型复唤。與其類似的是健田,還有 declare letdeclare const,使用 let 與使用 var 沒什么區(qū)別佛纫,但是使用 const 定義時(shí)妓局,表示此時(shí)的全局變量是一個(gè)常量。一般來說呈宇,全局變量都是禁止修改的好爬,所以大部分情況都應(yīng)該使用 const 而不是 varlet。需要注意的是攒盈,在聲明語句中只能定義類型抵拘,切勿在聲明語句中定義具體的值。

// 全局變量foo包含了存在組件總數(shù)型豁。
declare var foo: number;
declare function

declare function 用來定義全局函數(shù)的類型僵蛛。jQuery 其實(shí)就是一個(gè)函數(shù),所以也可以用 function 來定義:

declare function jQuery(selector: string): any;

在函數(shù)類型的聲明語句中迎变,函數(shù)重載也是支持的:

declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
declare class

當(dāng)全局變量是一個(gè)類的時(shí)候充尉,我們用 declare class 來定義它的類型:

declare class Animal {
  name: string;
  constructor(name: string);
  sayName(): void
}

同樣的,declare class 語句也只能用來定義類型衣形,不能用來定義具體的值驼侠,比如定義 sayName 方法的具體實(shí)現(xiàn)則會(huì)報(bào)錯(cuò):

declare class Animal {
  name: string;
  constructor(name: string);
  sayName() { } // error TS1183: An implementation cannot be declared in ambient contexts.
}
declare enum

使用 declare enum 定義的枚舉類型也稱作外部枚舉,如:

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

其中谆吴,Directions 是由第三方庫定義好的全局變量倒源。

declare namespace

namespace 是 ts 早期為了解決模塊化而創(chuàng)造出來的關(guān)鍵字,中文稱為命名空間句狼。由于歷史原因笋熬,在早期還沒有 ES6 的時(shí)候,ts 提供了一種模塊化方案腻菇,使用 module 關(guān)鍵字表示內(nèi)部模塊胳螟。但由于后來 ES6 也使用了 module 關(guān)鍵字, ts 為了兼容 ES6筹吐,使用 namespace 替代了自己的 module糖耸,更名為命名空間。隨著 ES6 的廣泛應(yīng)用丘薛,現(xiàn)在已經(jīng)不建議再使用 ts 中的 namespace嘉竟,而推薦使用 ES6 的模塊化方案,故我們不再需要學(xué)習(xí) namespace 的使用了。namespace 被淘汰了周拐,但是在聲明文件中铡俐,declare namespace 還是比較常用的,它用來表示全局變量是一個(gè)對(duì)象妥粟,包含很多屬性审丘。

比如,jQuery 是一個(gè)全局變量勾给,它是一個(gè)對(duì)象滩报,提供了一個(gè) jQuery.ajax 方法可以調(diào)用,那么我們就應(yīng)該使用 declare namespace jQuery 來聲明這個(gè)擁有多個(gè)子屬性的全局變量播急。

declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
}

jQuery.ajax('/api/get_something');

declare namespace 內(nèi)部脓钾,我們直接使用 function ajax 來聲明函數(shù),而不是使用 declare function ajax桩警。類似的可训,也可以使用 constclass捶枢,enum等語句握截。

declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
  const version: number;
  class Event {
    blur(eventType: EventType): void
  }
  enum EventType {
    CustomClick
  }
}

jQuery.ajax('/api/get_something');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick);

在編譯之后,declare namespace 內(nèi)的所有內(nèi)容都會(huì)被刪除烂叔。

嵌套的命名空間

如果對(duì)象擁有更深的層級(jí)谨胞,則需要使用 namespace 來聲明深層的屬性類型:

declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
  namespace fn {
    function extend(object: any): void;
  }
}

jQuery.ajax('/api/get_something');
jQuery.fn.extend({
  check: function() {
      return this.each(() => {
          this.checked = true;
      });
  }
});

假如 jQuery 下僅有 fn 這一個(gè)屬性(沒有 ajax 等其他屬性或方法),則可以不需要嵌套 namespace

declare namespace jQuery.fn {
  function extend(object: any): void;
}

jQuery.fn.extend({
  check: function () {
    return this.each(() => {
      this.checked = true;
    });
  }
});
interface 和 type

除了全局變量之外蒜鸡,有一些類型我們可能也希望暴露出來胯努。在類型聲明文件中,我們可以直接使用 interfacetype 來聲明一個(gè)全局的類型:

interface IAjaxSetting {
  method: "GET" | "POST";
  data?: any;
}

declare namespace jQuery {
  function ajax(url: string, settings?: IAjaxSetting): void;
}

這樣的話逢防,在其他文件中也可以使用這個(gè)接口了:

const setting: IAjaxSetting = {
  method: "GET"
};

jQuery.ajax('/api/post_something', setting);

typeinterface 類似叶沛,不在贅述。

防止命名沖突

暴露在最外層的 interfacetype 會(huì)作為全局類型作用于整個(gè)項(xiàng)目中忘朝,我們應(yīng)該盡可能的減少全局變量或全局類型的數(shù)量灰署,因此,將他們放在 namespace 下:

declare namespace jQuery {
  interface IAjaxSetting {
    method: "GET" | "POST";
    data?: any;
  }
  function ajax(url: string, settings?: IAjaxSetting): void;
}

那么辜伟,在使用 IAjaxSetting 接口的時(shí)候氓侧,也應(yīng)該加上 jQuery 前綴了:

const setting: jQuery.IAjaxSetting = {
  method: "GET"
};
聲明合并

jQuery 即是一個(gè)函數(shù)脊另,可以直接調(diào)用导狡,也可以是一個(gè)對(duì)象,擁有子屬性偎痛,則我們可以組合多個(gè)聲明語句旱捧,他們會(huì)不沖突的合并起來:

declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
declare namespace jQuery {
  interface IAjaxSetting {
    method: "GET" | "POST";
    data?: any;
  }
  function ajax(url: string, settings?: IAjaxSetting): void;
}

const setting: jQuery.IAjaxSetting = {
  method: "GET"
};
jQuery("#app");
jQuery(() => { });
jQuery.ajax('/api/post_something', setting);

npm 包

一般我們通過 import xxx from "xxx" 導(dǎo)入一個(gè) npm 包,這是符合 ES6 模塊規(guī)范的。當(dāng)我們嘗試給一個(gè) npm 包創(chuàng)建聲明文件之前枚赡,首先看看它的聲明文件是否存在氓癌。一般來說,npm 包的聲明文件可能存在于兩個(gè)地方:

  1. 與該 npm 包綁定在一起贫橙。判斷依據(jù)是 package.json 中有 types 字段贪婉,或者有一個(gè) index.d.ts 聲明文件。這種模式不需要額外安裝其他包卢肃,是最為推薦的疲迂,所以以后我們自己創(chuàng)建 npm 包的時(shí)候,最好也將聲明文件與 npm 包綁定在一起莫湘。

  2. 發(fā)布到了 @types 里只要嘗試安裝一下對(duì)應(yīng)的包就知道是否存在尤蒿,安裝命令是 npm install @types/xxx --save-dev。這種模式一般是由于 npm 包的維護(hù)者沒有提供聲明文件幅垮,所以只能由其他人將聲明文件發(fā)布到 @types 里了腰池。

假如以上兩種方式都沒有找到對(duì)應(yīng)的聲明文件,那么我們就需要自己為它寫聲明文件了忙芒。憂郁是通過 import 語句導(dǎo)入的模塊示弓,所以聲明文件存放的位置也有所約束,一般有兩種方案:

  1. 創(chuàng)建一個(gè) node_modules/@types/xxx/index.d.ts 文件匕争,存放 xxx 模塊的聲明文件避乏。這種方式不需要額外的配置,但是 node_modules 目錄不穩(wěn)定甘桑,代碼也沒有被保存到倉庫中拍皮,無法回溯版本,有不小心被刪除的風(fēng)險(xiǎn)跑杭。

  2. 創(chuàng)建一個(gè) types 目錄铆帽,專門用來管理自己寫的聲明文件爹橱,將 xxx 的聲明文件放到 types/xxx/index.d.ts 中组砚。這種方式需要配置下 tsconfig.jsonpathsbaseUrl 字段。

目錄結(jié)構(gòu):

├── README.md
├── src
|  └── index.ts
├── types
|  └── xxx
|     └── index.d.ts
└── tsconfig.json

tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",         /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
        "baseUrl": "./",
        "paths": {
            "*" : ["types/*"]
        }
    }
}

如何配置之后事扭,通過 import 導(dǎo)入 xxx 的時(shí)候,也會(huì)去 types 目錄下尋找對(duì)應(yīng)的模塊聲明文件了腥泥。

module 配置可以有很多種選項(xiàng)溯乒,不同的選項(xiàng)會(huì)影響到導(dǎo)入導(dǎo)出模式。

不管采用了以上兩種方式中的哪一種艾君,我都強(qiáng)烈建議大家將書寫好的聲明文件(通過給原作者發(fā) pr虹茶,或者直接提交到 @types 里)發(fā)布到開源社區(qū)中董济,享受了這么多社區(qū)的優(yōu)秀的資源洲炊,就應(yīng)該在力所能及的時(shí)候給出一些回饋。只有所有人都參與進(jìn)來询微,才能讓 ts 社區(qū)更加繁榮。

export

npm 包的聲明文件與全局變量的聲明文件有很大的區(qū)別狂巢。在 npm 包的聲明文件中藻雌,使用 declare 不再會(huì)聲明一個(gè)全局變量做个,而只會(huì)在當(dāng)前文件中聲明一個(gè)局部變量居暖。只有在聲明文件中使用 export 導(dǎo)出,然后在引入方 import 導(dǎo)入后藤肢,才會(huì)應(yīng)用到這些類型的聲明太闺。

export 的語法與非聲明文件中的語法類似,區(qū)別僅在于聲明文件中禁止定義具體的值:

export const name: string;
export function getName(): string;
export class Animal {
  constructor(name: string);
  sayHi(): string;
}
export enum Directions {
  Up,
  Down,
  Left,
  Right
}
export interface Options {
  data: any;
}

對(duì)應(yīng)的導(dǎo)入和使用模塊應(yīng)該是這樣:

import { name, getName, Animal, Directions, Options } from "foo";
console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
  data: {
    name: 'foo'
  }
}

混用 declare 和 export

我們也可以使用 declare 先聲明多個(gè)變量嘁圈,最后再用 export 一次性導(dǎo)出省骂。上例的聲明文件可以等價(jià)的改寫成:

declare const name: string;
declare function getName(): string;
declare class Animal {
  constructor(name: string);
  sayHi(): string;
}
declare enum Directions {
  Up,
  Down,
  Left,
  Right
}
interface Options {
  data: any;
}

export {
  name,
  getName,
  Animal,
  Directions,
  Options
}

與全局變量的聲明文件類似,interface 前是不需要 declare 的最住。

export namespace

declare namespace 類似冀宴,export namespace 也是用來導(dǎo)出一個(gè)擁有子屬性的對(duì)象:

// types/foo/index.d.ts
export namespace foo {
  const name: string;
  namespace bar {
    function baz(): string;
  }
}

// src/main.ts
import { foo } from "./types/foo/index.d"
console.log(foo.name);
foo.bar.baz();

export default

在 ES6 模塊系統(tǒng)中,使用 export default 可以導(dǎo)出一個(gè)默認(rèn)值温学,引入方可以使用 import foo from "foo"略贮,而不是使用 import { foo } from "foo" 來導(dǎo)入這個(gè)默認(rèn)值。在類型聲明文件中仗岖,export default 用來導(dǎo)出默認(rèn)值的類型:

// types/foo/index.d.ts
export default function foo(): string;

// src/index.ts
import foo from 'foo';
foo();

注意逃延,只有 functionclassinterface 可以直接默認(rèn)導(dǎo)出轧拄,其他的變量需要先定義出來揽祥,再默認(rèn)導(dǎo)出:

export default enum Directions {  // error TS1109: Expression expected.
  Up,
  Down,
  Left,
  Right
}

上例中,export default enum 是錯(cuò)誤的語法檩电,需要先使用 declare enum 定義出來拄丰,再使用 export default 導(dǎo)出:

export default Directions;

declare enum Directions {  
  Up,
  Down,
  Left,
  Right
}

如上府树,針對(duì)這種默認(rèn)導(dǎo)出,我們一般會(huì)將導(dǎo)出語句房子啊整個(gè)聲明文件的最前面料按。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奄侠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子载矿,更是在濱河造成了極大的恐慌垄潮,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闷盔,死亡現(xiàn)場離奇詭異弯洗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逢勾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門牡整,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溺拱,你說我怎么就攤上這事果正。” “怎么了盟迟?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵秋泳,是天一觀的道長。 經(jīng)常有香客問我攒菠,道長迫皱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任辖众,我火速辦了婚禮卓起,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凹炸。我一直安慰自己戏阅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布啤它。 她就那樣靜靜地躺著奕筐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪变骡。 梳的紋絲不亂的頭發(fā)上离赫,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音塌碌,去河邊找鬼渊胸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛台妆,可吹牛的內(nèi)容都是我干的翎猛。 我是一名探鬼主播胖翰,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼切厘!你這毒婦竟也來了萨咳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤迂卢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后桐汤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體而克,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年怔毛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了员萍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拣度,死狀恐怖碎绎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抗果,我是刑警寧澤筋帖,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站冤馏,受9級(jí)特大地震影響日麸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逮光,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一代箭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涕刚,春花似錦嗡综、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至驾茴,卻和暖如春戴陡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沟涨。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國打工恤批, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人裹赴。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓喜庞,卻偏偏與公主長得像诀浪,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子延都,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容