一篇長文總結 TypeScript
須要掌握的基礎,但願對正在學或者想學 TypeScript
的看完這篇文章後的你,會對 TypeScript
有一個初步的理解。html
本文偏長,點贊👍獲取一塊記憶麪包typescript
新建一個文件夾,如 TypeScript-Study
,輸入以下命令行,在全局環境下安裝 tsc
命令npm
npm install -g typescript
複製代碼
安裝完成以後,咱們就能夠在任何地方執行 tsc
命令了。編程
ts
文件新建一個簡單的文件 hello.ts
,複製以下代碼到文件中,這裏建議使用 VS Code
編譯器,內置了 TypeScript
支持,並且自己也是用 TypeScript
編寫的(畢竟微軟的親兒子)json
let hello: string = 'hello typescript'
console.log(hello)
複製代碼
而後輸入下面命令行執行後端
tsc hello.ts
複製代碼
這時會發現文件夾多出了一個編譯好的 js
文件 hello.js
數組
var hello = 'hello typescript';
console.log(hello);
複製代碼
這裏能夠發現,在 ts
文件中使用 :
指定變量類型。bash
上面例子中咱們給 hello
參數指定了 string
類型,編譯成 JavaScript
時,檢查的代碼並不會被插入到 js
文件中。編程語言
這是由於 TypeScript
只會進行靜態檢查,若是發現有錯誤,編譯的時候就會報錯。編輯器
下面修改一下代碼,把定義了 string
類型的參數改成 number
類型的參數
let hello: string = 'hello typescript'
hello = 2
console.log(hello)
複製代碼
編輯器中會提示錯誤,編譯的時候也會出錯
但仍然不妨礙咱們執行生成 js
文件
var hello = 'hello typescript';
hello = 2;
console.log(hello);
複製代碼
若是要在報錯的時候終止 js
文件的生成,能夠在 tsconfig.json
中配置 noEmitOnError
便可,這裏就不展開說明。
JavaScript
的類型分爲兩種:原始數據類型和對象類型。
原始數據類型包括:布爾值、數值、字符串、null
、undefined
以及 Symbol
。
在 TypeScript
中,使用 boolean
定義布爾值類型
let isBoolean: boolean = true;
複製代碼
注意:要注意 boolean 和 Boolean 的大小寫區別, boolean 是布爾值類型,而 Boolean 是構造函數。這裏除了 null 和 undefined 以外,別的基本類型都同樣。
使用 number
定義數值類型
// 基本用法
let decLiteral: number = 1234567;
let hexLiteral: number = 0xf0ac;
let notANumber: number = NaN;
let infinity: number = Infinity;
複製代碼
編譯結果:
// 基本用法
var decLiteral = 1234567;
var hexLiteral = 0xf0ac;
var notANumber = NaN;
var infinity = Infinity;
複製代碼
使用 string
定義字符串類型
let name: string = 'sam';
複製代碼
使用 null
和 undefined
來定義這兩個原始數據類型:
let u: undefined = undefined;
let n: null = null;
複製代碼
在 TypeScript
中,咱們使用 interfaces
來定義對象類型。
我們直接舉例說明吧
interface IPerson {
name: string;
age: number;
}
let sam: IPerson = {
name: 'sam',
age: 20
};
複製代碼
上面的例子中,咱們定義了一個接口 IPerson
,而後定義了一個變量 sam
,變量的類型是 IPerson
,這樣就約束了 sam
的形狀必須和接口 IPerson
一致。
注意:爲了良好的編寫習慣,建議接口的名稱加上 I 前綴。
定義的變量 sam
比接口多或者少屬性都是會報錯的
interface IPerson {
name: string;
age: number;
}
// 錯誤(比接口少了個age屬性)
let sam1: IPerson = {
name: 'sam'
};
// 錯誤(比接口多了個gender屬性)
let sam2: IPerson = {
name: 'sam',
age: 20,
gender: 'man'
};
// 正確
let sam3: IPerson = {
name: 'sam',
age: 20
};
複製代碼
那麼怎麼解決這個問題呢,這裏有個可選屬性
在接口上某個屬性加個 ?
,代表不須要強制匹配該屬性
interface IPerson {
name: string;
age?: number;
}
// 正確
let sam1: IPerson = {
name: 'sam'
};
// 錯誤(此時咱們仍是不能在接口上添加未定義的屬性)
let sam2: IPerson = {
name: 'sam',
gender: 'man'
};
複製代碼
爲了解決添加未定義的屬性,咱們可使用任意屬性
在須要添加任意屬性的接口使用 [propName: string]
, 一旦定義了任意屬性,那麼肯定屬性和可選屬性的類型都必須是它的類型的子集
interface IPerson {
name: string;
// 此時的age類型爲number,不是string類型的子集
age?: number;
// 肯定屬性和可選屬性的類型都必須是string類型的子集
[propName: string]: string;
}
// 錯誤(此時的age類型爲number,不是string類型的子集)
let sam1: IPerson = {
name: 'sam',
age: 20,
gender: 'man'
};
複製代碼
那麼怎麼解決這個問題呢,一個接口中只能定義一個任意屬性。若是接口中有多個類型的屬性,則能夠在任意屬性中使用聯合類型
interface IPerson {
name: string;
age?: number;
// 此時肯定屬性和可選屬性的類型都必須是string或者number類型的子集
[propName: string]: string | number;
}
// 正確
let sam1: IPerson = {
name: 'sam',
age: 20,
gender: 'man'
};
// 正確
let sam2: IPerson = {
name: 'sam',
gender: 'man'
};
// 正確
let sam3: IPerson = {
name: 'sam'
};
// 錯誤,任意屬性未添加boolean類型
let sam4: IPerson = {
name: 'sam',
rich: false
};
複製代碼
對象中的一些字段只能在建立的時候被賦值,後續沒法更改
interface IPerson {
readonly id: number;
name: string;
age?: number;
[propName: string]: string | number;
}
let sam: IPerson = {
id: 123,
name: 'Tom',
gender: 'male'
};
// 錯誤,此時不能再次修改id的值
sam.id = 9527;
// Cannot assign to 'id' because it is a read-only property.
複製代碼
到這,我想你大概就瞭解了對象的類型(接口)了
還記得上面的例子中,咱們在給 hello
賦值的時候編譯器會報錯
let hello: string = 'hello typescript'
hello = 2
console.log(hello)
// Type '2' is not assignable to type 'string'.ts(2322)
複製代碼
若是把 hello
改成 any
類型,則代表 hello
能夠賦值爲任意類型。
let hello: any = 'hello typescript'
hello = 2
console.log(hello) // 2
複製代碼
固然這裏不是但願你全部的類型都用 any
,這還寫個🔨的 TypeScript
,這裏通常建議引入第三方庫或者實在沒法確認在處理什麼類型時的時候才使用。
若是沒有明確的指定類型,那麼 TypeScript
會依照類型推論(Type Inference
)的規則推斷出一個類型。
下面代碼 hello
沒有指定類型,但在編譯器和編譯時也會報錯誤提醒
let hello = 'hello typescript'
hello = 2
// Type '2' is not assignable to type 'string'.ts(2322)
複製代碼
由於它等價於
let hello: string = 'hello typescript'
hello = 2
複製代碼
可是若是定義的時候沒有賦值,無論以後有沒有賦值,都會被推斷成 any
類型
let hello
hello = 'string2'
hello = 2
複製代碼
等價於
let hello: any
hello = 'string2'
hello = 2
複製代碼
有時候咱們但願聲明一個變量時候包含多個類型,那麼咱們可使用 |
分隔每一個類型。還記得上面的例子嗎
let hello: string = 'hello typescript'
hello = 2
console.log(hello)
複製代碼
此時咱們修改一下 hello
參數
let hello: string | number = 'hello typescript'
hello = 2
console.log(hello)
複製代碼
這樣就代表 hello
參數接受 string
和 number
類型
在 TypeScript
中,數組類型有多個定義的方式。因爲本文是基礎篇,筆者只講比較經常使用的幾種方法方便你們理解,後續進階的用法筆者會另開文章說明。
let arr1: number[] = [1, 2, 3, 4, 5];
let arr2: string[] = ["one", "two", "third", "four", "five"];
let arr3: any[] = [1, "two", false, "four", 5];
複製代碼
但這裏要注意,定義後的數組 arr
的項中不容許出現非其餘的類型( any
除外)
// 錯誤(two爲string類型)
let arr1: number[] = [1, 'two', 3, 4, 5];
let arr2: number[] = [1, 2, 3, 4, 5];
// 錯誤(定義後的arr,push一個string類型是錯誤的)
arr2.push("six")
複製代碼
用數組泛型(Array Generic) Array<elemType>
來表示數組
泛型(Generics)是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性,這裏不作過多描述。
let arr1: Array<number> = [1, 2, 3, 4, 5];
let arr2: Array<string> = ["one", "two", "third", "four", "five"];
let arr3: Array<string | number | boolean> = [1, "two", false, "four", 5];
let arr4: Array<any> = [1, "two", false, "four", 5];
複製代碼
咱們先來複習一下 JavaScript
裏面聲明常見的定義函數的方式(函數聲明和函數表達式)
// 函數聲明
function sum1(x + y) {
return x + y;
}
// 函數表達式
let sum2 = function (x, y) {
return
}
複製代碼
在 TypeScript
聲明函數時,咱們須要把函數的輸入和輸出都要考慮在內
function sum1 (x: number + y: number): number {
return x + y;
}
// 用法
// 錯誤
sum(1);
sum(1, 2, 3);
// 正確
sum(1, 2);
複製代碼
可見,定義好的函數,若是輸入了多的或者少的參數都是不被 TypeScript
容許的。
你可能會這樣寫一個函數表達式
let sum2 = function (x: number, y: number): number {
return x + y;
};
複製代碼
這樣只對右邊的匿名函數進行了類型定義,而左邊的參數 sum2
是經過類型推斷出來的,並不算一個完整的 TypeScript
表達式。正確寫法以下
let sum2: (x: number, y: number) => number = function(x: number, y: number): number {
return x + y
}
複製代碼
注意:在 TypeScript 的類型定義中的 => 用來表示函數的定義,箭頭左邊是輸入類型,箭頭右邊是輸出類型,不要和 ES6 的箭頭函數混淆。
和接口的可選屬性類似,咱們也能夠用 ?
來給函數表示可選的參數
function sum3 (x:number, y?: number) {
return x + y
}
// 用法
sum3(1,2)
sum3(1)
複製代碼
注意:可選參數的後面不容許放必須參數,由於這樣調用的時候沒法識別。
除非添加參數默認值,則可無視這個限制
// 錯誤寫法(可選參數y後面跟着必須參數z)
function sum3 (x:number, y?:number, z: number) {
return x + y + z
}
// 給可選參數y添加參數默認值
function sum4 (x:number, y:number = 1, z: number) {
return x + y + z
}
複製代碼
用來手動指定一個值的類型叫作類型斷言。
有兩種寫法,尖括號 <類型>
和 as
let str: string = "this is a string";
// 尖括號<>
let strLength1: number = (<string>str).length;
// as
let strLength2: number = (str as string).length;
複製代碼
這裏建議你們統一使用 as 語法,由於在 React 的 tsx 語法中必須使用 as。
當 TypeScript
不肯定一個聯合類型的變量究竟是哪一個類型的時候,咱們只能訪問此聯合類型的全部類型裏共有的屬性或方法,如
function getLength(str: string | number): number {
// 錯誤,由於 number 類型沒有 length 方法
return str.length;
}
// Property 'length' does not exist on type 'number'.ts(2339)
複製代碼
爲此,咱們須要用到類型斷言
function getLength(str: string | number): number {
if (typeof str === 'number') {
// 此時將 str 的類型判斷爲 number 類型,可使用 number 類型的屬性和方法
return (str as number).toString().length
} else if (typeof str === 'string') {
// 此時將 str 的類型判斷爲 string 類型,可使用 string 類型的屬性和方法
return (str as string).length
} else {
throw 'error'
}
}
複製代碼
恭喜你👏看到這裏,TypeScript
最爲須要掌握的基礎你已經學完了,不太重頭戲纔剛剛開始😅
枚舉是 TypeScript
中,對 JavaScript
標準數據類型的補充,例如一個 Http
包含哪些狀態,一星期是從星期一到星期日等,咱們均可以用枚舉表達,若是你用事後端的編程語言,應該對枚舉不陌生。
不一樣於對象定義的 key-value 中,只能經過 key 去訪問 value 的值,在 enum 中,既能夠經過 key 訪問 value 的值,也能夠經過 value 訪問 key 的值。
枚舉用 enum
關鍵字來定義
enum Person {
Male,
Female
}
複製代碼
上面的代碼編譯以下
var Person;
(function (Person) {
Person[Person["Male"] = 0] = "Male";
Person[Person["Female"] = 1] = "Female";
})(Person || (Person = {}));
複製代碼
可見,若沒給枚舉的成員賦值,那麼會默認從 0
開始遞增。
固然咱們也能夠給枚舉手動賦值
enum Person {
Male = 7,
Female // 此時 Female 會爲 8
}
// 由於未賦值的枚舉會接着上一個枚舉項遞增,所以此時 Female 會爲 8
複製代碼
上面提到的都是常數項,其實枚舉還有一個類型叫 計算所得項
enum Person {
Male, // 常數項
Female = "female".length // 計算所得項
}
// 若是計算所得項後面是沒有賦值的項,則會報錯
// 錯誤
enum Person {
Male = "man".length,
Female
}
// 正確
enum Person {
Male = "man".length,
Female = 8
}
複製代碼
常數枚舉是用 const enum
定義的,常數枚舉在編譯的階段會被刪除,既在編譯後的文件不存在編譯後的常數枚舉,且不能包含計算成員
// 正確
const enum Person {
Male,
Female
}
// 錯誤
const enum Person2 {
Male,
Female = "female".length
}
let person = [Person.Male, Person.Female]
複製代碼
編譯結果
var person = [0 /* Male */, 1 /* Female */];
複製代碼
外部枚舉是用 declare enum
定義的,外部枚舉用來描述已經存在的枚舉類型的形狀。
declare enum Person {
Male,
Female
}
let person = [Person.Male, Person.Female]
複製代碼
編譯結果
var person = [Person.Male, Person.Female];
複製代碼
元組合並了不一樣類型的對象,須要以元組所定義的順序預約數據類型。
下面舉個簡單的例子
let tuple1: [string, number];
// 正確
tuple1 = ["one", 2];
// 錯誤
tuple1 = ["one", "two"];
tuple1 = [1, 2];
tuple1 = ["one", 2, 3]; // 未在元組中定義
tuple1 = [true, 2] // 元組在index爲0中只接受string類型
複製代碼
還記得上文數組泛型的定義嗎
let arr1: Array<number> = [1, 2, 3, 4, 5];
複製代碼
這裏的 Array<number>
只容許數組的每一項都要爲 number
類型,但有的時候,咱們但願返回值的類型與傳入參數的類型是相同的,所以有了泛型。
這裏是官方文檔的例子:
function identity<T>(arg: T): T {
return arg;
}
複製代碼
這裏的 identity
函數就被稱爲泛型,由於它能夠用於多個類型,咱們給 identity
添加了類型變量 T
。 T
會幫助咱們捕獲用戶傳入的類型(好比:number),以後咱們就可使用這個類型。 以後咱們再次使用了 T
當作返回值類型。
定義了泛型以後,咱們能夠這樣使用
let output1 = identity<string>("myString");
// 類型推論。編譯器會根據傳入的參數自動地幫助咱們肯定 T 的類型
// 此時的 T 爲 string 類型
let output2 = identity("myString");
// 此時的 T 爲 number 類型
let out2 = identity(123)
// 此時的 T 爲 boolean 類型
let out3 = identity(true)
複製代碼
若是咱們想打印 arg
的長度,會發現編譯器報錯。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length return arg; } 複製代碼
這是由於泛型 T
是默認把 arg
參數看成是任意或者全部類型,而 number
類型沒有 length
屬性,因此會報錯。
若是咱們傳入數字數組,將返回一個數字數組,由於此時 T
的的類型爲 number
。 這可讓咱們把泛型變量 T
當作類型的一部分使用,而不是整個類型,增長了靈活性。
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // arg
return arg;
}
複製代碼
咱們把上面例子的對象字面量換位接口
interface Iidentity {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: Iidentity = identity;
複製代碼
與泛型接口相似,泛型也能夠用於類的類型定義中
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; };
複製代碼
看到這裏,你還敢說不了解 TypeScript
嗎?若是還不瞭解,那必定是你還沒給這篇文章點贊👍👍👍!!!
看得不過癮?這裏還有別的文章