一、什么是 TypeScript
TypeScript 是 JavaScript 的一個(gè)超集,主要提供了類型系統(tǒng)和對(duì) ES6 的支持,它由 Microsoft 開發(fā),代碼開源于 GitHub 上。
二、為什么選擇 TypeScript
1.TypeScript 增加了代碼的可讀性和可維護(hù)性
- 類型系統(tǒng)實(shí)際上是最好的文檔,大部分的函數(shù)看看類型的定義就可以知道如何使用了
- 可以在編譯階段就發(fā)現(xiàn)大部分錯(cuò)誤,這總比在運(yùn)行時(shí)候出錯(cuò)好
- 增強(qiáng)了編輯器和 IDE 的功能,包括代碼補(bǔ)全、接口提示、跳轉(zhuǎn)到定義、重構(gòu)等
2.TypeScript 非常包容
- TypeScript 是 JavaScript 的超集,
.js文件可以直接重命名為.ts即可 - 即使不顯式的定義類型,也能夠自動(dòng)做出類型推論
- 可以定義從簡(jiǎn)單到復(fù)雜的幾乎一切類型
- 即使 TypeScript 編譯報(bào)錯(cuò),也可以生成 JavaScript 文件
- 兼容第三方庫(kù),即使第三方庫(kù)不是用 TypeScript 寫的,也可以編寫單獨(dú)的類型文件供 TypeScript 讀取
3.TypeScript 擁有活躍的社區(qū)
- 大部分第三方庫(kù)都有提供給 TypeScript 的類型定義文件
- Google 開發(fā)的 Angular2 就是使用 TypeScript 編寫的
- TypeScript 擁抱了 ES6 規(guī)范,也支持部分 ESNext 草案的規(guī)范
4.TypeScript 的缺點(diǎn)
- 有一定的學(xué)習(xí)成本,需要理解接口(Interfaces)、泛型(Generics)、類(Classes)、枚舉類型(Enums)等前端工程師可能不是很熟悉的概念
- 短期可能會(huì)增加一些開發(fā)成本,畢竟要多寫一些類型的定義,不過對(duì)于一個(gè)需要長(zhǎng)期維護(hù)的項(xiàng)目,TypeScript 能夠減少其維護(hù)成本
- 集成到構(gòu)建流程需要一些工作量
- 可能和一些庫(kù)結(jié)合的不是很完美
三、安裝 TypeScript
TypeScript 的命令行工具安裝方法如下:
npm install -g typescript
以上命令會(huì)在全局環(huán)境下安裝 tsc 命令,安裝完成之后,我們就可以在任何地方執(zhí)行 tsc 命令了。
編譯一個(gè) TypeScript 文件很簡(jiǎn)單:
tsc hello.ts
我們約定使用 TypeScript 編寫的文件以 .ts 為后綴,用 TypeScript 編寫 React 時(shí),以 .tsx 為后綴。
四、TypeScript基礎(chǔ)
1.定義原始數(shù)據(jù)類型
JavaScript 的類型分為兩種:原始數(shù)據(jù)類型(Primitive data types)和對(duì)象類型(Object types)。
原始數(shù)據(jù)類型包括:布爾值、數(shù)值、字符串、null、undefined 以及 ES6 中的新類型 Symbol。
let isDone: boolean = false; //布爾值
let decLiteral: number = 6; //數(shù)值
let myName: string = 'Tom'; //字符串
//JavaScript 沒有空值(Void)的概念,在 TypeScript 中,可以用 void 表示沒有任何返回值的函數(shù):
function alertName(): void {
alert('My name is Tom');
}
//undefined 類型的變量只能被賦值為 undefined,null 類型的變量只能被賦值為 null。
let u: undefined = undefined;
let n: null = null;
TypeScript 中,使用 : 指定變量的類型。TypeScript 只會(huì)進(jìn)行靜態(tài)檢查,如果發(fā)現(xiàn)有錯(cuò)誤,編譯的時(shí)候就會(huì)報(bào)錯(cuò)。TypeScript 編譯的時(shí)候即使報(bào)錯(cuò)了,還是會(huì)生成編譯結(jié)果,我們?nèi)匀豢梢允褂眠@個(gè)編譯之后的文件。
2.定義任意值類型
任意值(Any)用來表示允許賦值為任意類型。
如果是一個(gè)普通類型,在賦值過程中改變類型是不被允許的:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
但如果是 any 類型,則允許被賦值為任意類型。在任意值上訪問任何屬性都是允許的,也允許調(diào)用任何方法
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
let anyThing: any = 'hello';
console.log(anyThing.myName.firstName);
anyThing.setName('Jerry').sayHello();
可以認(rèn)為,聲明一個(gè)變量為任意值之后,對(duì)它的任何操作,返回的內(nèi)容的類型都是任意值。
變量如果在聲明的時(shí)候,未指定其類型,那么它會(huì)被識(shí)別為任意值類型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
3.類型推論
如果沒有明確的指定類型,那么 TypeScript 會(huì)依照類型推論(Type Inference)的規(guī)則推斷出一個(gè)類型。
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
如果定義的時(shí)候沒有賦值,不管之后有沒有賦值,都會(huì)被推斷成 any 類型而完全不被類型檢查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
4.聯(lián)合類型
聯(lián)合類型(Union Types)表示取值可以為多種類型中的一種。聯(lián)合類型使用 | 分隔每個(gè)類型。
let myFavoriteNumber: string | number;
myFavoriteNumber = true;
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
//這里的 let myFavoriteNumber: string | number 的含義是,允許 myFavoriteNumber 的類型是 string 或者 number,但是不能是其他類型。
當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法:
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
上例中,length 不是 string 和 number 的共有屬性,所以會(huì)報(bào)錯(cuò)。
5.接口(Interfaces)
TypeScript 中的接口是一個(gè)非常靈活的概念,它可用于對(duì)類的一部分行為進(jìn)行抽象以外,也常用于對(duì)「對(duì)象的形狀(Shape)」進(jìn)行描述。
- 1?? 對(duì) 「對(duì)象的形狀(Shape)」進(jìn)行描述
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
上面的例子中,我們定義了一個(gè)接口 Person,接著定義了一個(gè)變量 tom,它的類型是 Person。這樣,我們就約束了 tom 的形狀必須和接口 Person 一致。
接口一般首字母大寫,定義的變量比接口少了一些屬性是不允許的,多一些屬性也是不允許的。賦值的時(shí)候,變量的形狀必須和接口的形狀保持一致
可選屬性
有時(shí)我們希望不要完全匹配一個(gè)形狀,那么可以用 可選屬性:
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
可選屬性的含義是該屬性可以不存在。
任意屬性
有時(shí)候我們希望一個(gè)接口允許有任意的屬性,可以使用如下方式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
使用 [propName: string] 定義了任意屬性取 string 類型的值。
需要注意的是,一旦定義了任意屬性,那么確定屬性和可選屬性的類型都必須是它的類型的子集:
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
上例中,任意屬性的值允許是 string,但是可選屬性 age 的值卻是 number,number 不是 string 的子屬性,所以報(bào)錯(cuò)了。
只讀屬性
有時(shí)候我們希望對(duì)象中的一些字段只能在創(chuàng)建的時(shí)候被賦值,那么可以用 readonly 定義只讀屬性:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,使用 readonly 定義的屬性 id 初始化后,又被賦值了,所以報(bào)錯(cuò)了。
注意,只讀的約束存在于第一次給對(duì)象賦值的時(shí)候,而不是第一次給只讀屬性賦值的時(shí)候
- 2?? 對(duì)類的一部分行為進(jìn)行抽象
實(shí)現(xiàn)(implements)是面向?qū)ο笾械囊粋€(gè)重要概念。一般來講,一個(gè)類只能繼承自另一個(gè)類,有時(shí)候不同類之間可以有一些共有的特性,這時(shí)候就可以把特性提取成接口(interfaces),用 implements 關(guān)鍵字來實(shí)現(xiàn)。這個(gè)特性大大提高了面向?qū)ο蟮撵`活性。
舉例來說,門是一個(gè)類,防盜門是門的子類。如果防盜門有一個(gè)報(bào)警器的功能,我們可以簡(jiǎn)單的給防盜門添加一個(gè)報(bào)警方法。這時(shí)候如果有另一個(gè)類,車,也有報(bào)警器的功能,就可以考慮把報(bào)警器提取出來,作為一個(gè)接口,防盜門和車都去實(shí)現(xiàn)它:
interface Alarm {
alert();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
interface Alarm {
fn();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
//錯(cuò)誤信息如下:
index.ts:22:7 - error TS2420: Class 'SecurityDoor' incorrectly implements interface 'Alarm'.
Property 'fn' is missing in type 'SecurityDoor' but required in type 'Alarm'.
22 class SecurityDoor extends Door implements Alarm {
~~~~~~~~~~~~
index.ts:16:5
16 fn();
~~~~~
'fn' is declared here.
index.ts:28:7 - error TS2420: Class 'Car' incorrectly implements interface 'Alarm'.
Property 'fn' is missing in type 'Car' but required in type 'Alarm'.
28 class Car implements Alarm {
~~~
index.ts:16:5
16 fn();
~~~~~
'fn' is declared here.
Found 2 errors.
一個(gè)類可以實(shí)現(xiàn)多個(gè)接口:
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
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)了 Alarm 和 Light 接口,既能報(bào)警,也能開關(guān)車燈。
接口繼承接口
接口與接口之間可以是繼承關(guān)系:
interface Alarm {
alert();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}
class Car implements LightableAlarm {
alert() {
console.log('SecurityDoor alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
上例中,我們使用 extends 使 LightableAlarm 繼承 Alarm。因此類car中需實(shí)現(xiàn)alert、lightOn、lightOff
接口繼承類
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 {
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;
6.定義數(shù)組的類型
在 TypeScript 中,數(shù)組類型有多種定義方式,比較靈活。
- 1?? 「類型 + 方括號(hào)」表示法
let fibonacci: number[] = [1, 1, 2, 3, 5];
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
- 2?? 數(shù)組泛型
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
- 3?? 用接口表示數(shù)組
nterface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
7.函數(shù)的類型
在 JavaScript 中,有兩種常見的定義函數(shù)的方式——函數(shù)聲明(Function Declaration)和函數(shù)表達(dá)式(Function Expression):
// 函數(shù)聲明(Function Declaration)
function sum(x, y) {
return x + y;
}
// 函數(shù)表達(dá)式(Function Expression)
let mySum = function (x, y) {
return x + y;
};
- 1?? 函數(shù)聲明
輸入多余的(或者少于要求的)參數(shù),是不被允許的:
function sum(x: number, y: number): number {
return x + y;
}
sum(1);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
我們用 ? 表示可選的參數(shù)
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
需要注意的是,可選參數(shù)必須接在必需參數(shù)后面。換句話說,可選參數(shù)后面不允許再出現(xiàn)必須參數(shù)了
TypeScript 會(huì)將添加了默認(rèn)值的參數(shù)識(shí)別為可選參數(shù)
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
剩余參數(shù)
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
重載
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
上例中,我們重復(fù)定義了多次函數(shù) reverse,前幾次都是函數(shù)定義,最后一次是函數(shù)實(shí)現(xiàn),最終實(shí)現(xiàn)一個(gè)函數(shù) reverse,輸入數(shù)字 123 的時(shí)候,輸出反轉(zhuǎn)的數(shù)字 321,輸入字符串 'hello' 的時(shí)候,輸出反轉(zhuǎn)的字符串 'olleh'
- 2?? 函數(shù)表達(dá)式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。在 TypeScript 的類型定義中,=> 用來表示函數(shù)的定義,左邊是輸入類型,需要用括號(hào)括起來,右邊是輸出類型。
- 3?? 用接口定義函數(shù)的形狀
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
8.類型斷言
<類型>值 或 值 as 類型
在 tsx 語(yǔ)法(React 的 jsx 語(yǔ)法的 ts 版)中必須用后一種。
有時(shí)候,我們確實(shí)需要在還不確定類型的時(shí)候就訪問其中一個(gè)類型的屬性或方法
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
// index.ts(3,26): 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ò)。此時(shí)可以使用類型斷言,將 something 斷言成 string:
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
或者
function getLength(something: string | number): number {
if ((something as string).length) {
return (something as string).length;
} else {
return something.toString().length;
}
}
類型斷言不是類型轉(zhuǎn)換,斷言成一個(gè)聯(lián)合類型中不存在的類型是不允許的:
9.聲明文件
當(dāng)使用第三方庫(kù)時(shí),我們需要引用它的聲明文件,才能獲得對(duì)應(yīng)的代碼補(bǔ)全、接口提示等功能。
詳情該頁(yè)面深入學(xué)習(xí)。
五、TypeScript進(jìn)階
1.創(chuàng)建類型別名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
2.字符串字面量類型
字符串字面量類型用來約束取值只能是某幾個(gè)字符串中的一個(gè)。
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 沒問題
handleEvent(document.getElementById('world'), 'dbclick'); // 報(bào)錯(cuò),event 不能為 'dbclick'
// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
3.元組
數(shù)組合并了相同類型的對(duì)象,而元組(Tuple)合并了不同類型的對(duì)象。
let xcatliu: [string, number] = ['Xcat Liu', 25];
當(dāng)添加越界的元素時(shí),它的類型會(huì)被限制為元組中每個(gè)類型的聯(lián)合類型:
let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25];
xcatliu.push('http://xcatliu.com/');
xcatliu.push(true);
// index.ts(4,14): error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
4.枚舉
枚舉(Enum)類型用于取值被限定在一定范圍內(nèi)的場(chǎng)景,比如一周只能有七天,顏色限定為紅綠藍(lán)等。
enum Weekday {sun,mon,tue,wed,thu,fri,sat};
let day: Weekday = Weekday.sun
編譯后的js代碼:
var Weekday;
(function (Weekday) {
Weekday[Weekday["sun"] = 0] = "sun";
Weekday[Weekday["mon"] = 1] = "mon";
Weekday[Weekday["tue"] = 2] = "tue";
Weekday[Weekday["wed"] = 3] = "wed";
Weekday[Weekday["thu"] = 4] = "thu";
Weekday[Weekday["fri"] = 5] = "fri";
Weekday[Weekday["sat"] = 6] = "sat";
})(Weekday || (Weekday = {}));
;
var day = Weekday.sun;
首個(gè)枚舉項(xiàng)的默認(rèn)值為 0。這點(diǎn)值得注意,在 JavaScript 中,0 是一個(gè)假值,那么就會(huì)出現(xiàn)一個(gè)問題,如果我想要判斷一個(gè)枚舉值是否存在怎么辦?我能直接 if (c) { ... } 這樣判斷嗎?顯然是不可靠的,請(qǐng)看下面代碼:
enum Color {Red, Green, Blue}
let c: Color;
if (!c) {
console.log('Yes, I am not defined.');
}
c = Color.Red;
if (!c) {
console.log('Oops, I have a valid value, but seems I am undefined!');
}
是不是,問題顯而易見,如果我們想要對(duì)此枚舉類型變量值進(jìn)行判斷,我們還得這么寫:typeof(c) !== 'undefined',非常繁瑣。而如果將首個(gè)枚舉項(xiàng)的值設(shè)置為 1,我們就可以放心地直接將變量放在條件判斷語(yǔ)句中進(jìn)行判斷了。
enum Color {Red =1, Green, Blue}
let c: Color;
5.類
TypeScript 可以使用三種訪問修飾符(Access Modifiers),分別是 public、private 和 protected
- public 修飾的屬性或方法是公有的,可以在任何地方被訪問到,默認(rèn)所有的屬性和方法都是 public 的
- private 修飾的屬性或方法是私有的,不能在聲明它的類的外部訪問
- protected 修飾的屬性或方法是受保護(hù)的,它和 private 類似,區(qū)別是它在子類中也是允許被訪問的
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
上面的例子中,name 被設(shè)置為了 public,所以直接訪問實(shí)例的 name 屬性是允許的。
很多時(shí)候,我們希望有的屬性是無法直接存取的,這時(shí)候就可以用 private 了:
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
使用 private 修飾的屬性或方法,在子類中也是不允許訪問的:
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
而如果是用 protected 修飾,則允許在子類中訪問:
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
給類加上 TypeScript 的類型很簡(jiǎn)單,與接口類似:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
6.泛型
泛型(Generics)是指在定義函數(shù)、接口或類的時(shí)候,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性。
首先,我們來實(shí)現(xiàn)一個(gè)函數(shù) createArray,它可以創(chuàng)建一個(gè)指定長(zhǎng)度的數(shù)組,同時(shí)將每一項(xiàng)都填充一個(gè)默認(rèn)值:
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
上例中,我們使用了之前提到過的數(shù)組泛型來定義返回值的類型。Array<any> 允許數(shù)組的每一項(xiàng)都為任意類型。但是我們預(yù)期的是,數(shù)組中每一項(xiàng)都應(yīng)該是輸入的 value 的類型。
這時(shí)候,泛型就派上用場(chǎng)了:
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<string>(3, 'x'); // ['x', 'x', 'x']
上例中,我們?cè)诤瘮?shù)名后添加了 <T>,其中 T 用來指代任意輸入的類型,在后面的輸入 value: T 和輸出 Array<T> 中即可使用了。
接著在調(diào)用的時(shí)候,可以指定它具體的類型為 string。當(dāng)然,也可以不手動(dòng)指定,而讓類型推論自動(dòng)推算出來:
定義泛型的時(shí)候,可以一次定義多個(gè)類型參數(shù):
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
上例中,我們定義了一個(gè) swap 函數(shù),用來交換輸入的元組。
泛型約束
在函數(shù)內(nèi)部使用泛型變量的時(shí)候,由于事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
上例中,泛型 T 不一定包含屬性 length,所以編譯的時(shí)候報(bào)錯(cuò)了。
這時(shí),我們可以對(duì)泛型進(jìn)行約束,只允許這個(gè)函數(shù)傳入那些包含 length 屬性的變量。這就是泛型約束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
上例中,我們使用了 extends 約束了泛型 T 必須符合接口 Lengthwise 的形狀,也就是必須包含 length 屬性。
此時(shí)如果調(diào)用 loggingIdentity 的時(shí)候,傳入的 arg 不包含 length,那么在編譯階段就會(huì)報(bào)錯(cuò)了
泛型也可以用于類的類型定義中:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
泛型參數(shù)的默認(rèn)類型
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;
}
7.聲明合并
如果定義了兩個(gè)相同名字的函數(shù)、接口或類,那么它們會(huì)合并成一個(gè)類型:
- 函數(shù)的合并
我們可以使用重載定義多個(gè)函數(shù)類型:
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
- 接口的合并
合并的屬性的類型必須是唯一的
interface Alarm {
price: number;
}
interface Alarm {
price: string; // 類型不一致,會(huì)報(bào)錯(cuò)
weight: number;
}
// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type. Variable 'price' must be of type 'number', but here has type 'string'.
接口中方法的合并,與函數(shù)的合并一樣:
interface Alarm {
price: number;
alert(s: string): string;
}
interface Alarm {
weight: number;
alert(s: string, n: number): string;
}
相當(dāng)于:
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}
- 類的合并
類的合并與接口的合并規(guī)則一致。