TypeScript是微軟推出的一個強類型JavaScript超集,具有了比JavaScript更嚴禁的類型定義,備受程序員喜好的編輯器VSCode就是使用TypeScript寫的,後面簡稱ts。程序員
在使用js的時候,一個變量能夠被任意賦值,並且無論你怎麼寫老是不會報錯,這就意味着不少時候錯誤被埋藏在看似正常的代碼中。不一樣於js的直接執行,ts代碼必須經過編譯成js來運行,不少錯誤會在編譯的時候就報錯,大大下降了發現低級錯誤的成本。typescript
尤爲在寫大型項目的時候,ts的優點更加明顯。例如咱們寫了一個組件,組件使用了一個傳過來的對象做爲props,忽然咱們須要給組件增長一個功能,須要多傳入一個數據,所以咱們須要給props對象添加一個屬性。若是使用js,這種狀況下肯定哪些地方須要作對應的修改是一件很頭疼的事情,而ts則很容易,咱們修改一下props對象的接口類型,全部須要修改的地方就都會報錯,依次修改報錯的地方就能夠了。數組
網上各種ts的教程不少,可是不多有站在一個不懂ts的立場上來寫的,這篇文章的目的,就是帶領那些熟悉js的人,一步步理解ts添加的類型模式,把一個陡峭的山坡,變成一級級的臺階,幫助想用ts的人快速入門。編輯器
在js中,不少的東西都是以對象的形式存在的,除了最基礎的5種數據類型:函數
這五種最基本的數據類型的定義是很直白的,就是冒號加名字,以下所示:ui
let num: number = 0;
let str: string = "";
let boo: boolean = false;
let foo: null = null;
let bar: undefined = undefined;
複製代碼
從上面的例子能夠看出,ts和js的區別,就是在變量後面緊跟了一個冒號,冒號後面寫下了變量的類型。記住這一點,下面的內容就能理解了,若是尚未理解,就須要反覆體會一下,理解了再繼續往下。spa
除了上面的這些類型之外,ts還增長了一種特殊的類型:void
指針
let a: void = null;
複製代碼
一個void類型的變量只能被賦值爲null或者undefined,它一般用在函數中,指定函數的輸出爲void意味着這個函數沒有返回值。code
除了上面介紹的6大基礎類型以外,還一個萬能類型:any
對象
let a: any = "hello,world!"
複製代碼
萬能類型就是js自己的樣子,一個變量能夠被定義爲任何類型,也是使用ts的時候應該儘可能避免使用的類型,使用過多的any就讓ts退化成了js,失去了使用ts的意義。
ts支持聯合類型,意味着一個變量能夠有不止一個類型,以下所示:
let name: string | number = 'mike';
name = 18; // 容許賦值兩種類型的數據
複製代碼
聯合類型賦予了ts變量必定的自由,可是還不如js那麼自由。對聯合類型的屬性和方法的訪問是受限的,在給name賦值以前只能訪問公共的屬性與方法,在賦值之後則只能訪問由被賦值的類型擁有的屬性與方法。
聯合類型是一種處於基礎類型與any類型之間的中間態。
有了基礎的類型,還須要一種方法來定義對象的類型,由於對象就是一些基礎類型拼湊起來的,因此缺乏的只是一個封裝的方法,所以ts引入了interface關鍵字。
聲明一個interface:
interface person {
name: string;
age: number;
}
複製代碼
而後就可使用這個interface來限制一個對象的類型:
let tom: person = {
name: 'tom',
age: 12
}
複製代碼
有了interface咱們就能夠把一堆基礎類型封裝成一個對象的類型。須要注意的是聲明接口的時候用的是;
分號,而對象中用的是,
逗號。
在對象中除了必須的屬性與方法之外,可能還有些屬性或方法不是必須的,因而就誕生了可選類型。在interface中放一個可選類型的方式以下所示:
interface person {
name: string;
age?: number; // 能夠沒有這個屬性
}
複製代碼
這樣咱們在申明一個類型爲person的對象時就能夠不給它age屬性,讓這個對象能夠變小。
一個對象中除了必須的類型和無關緊要的類型外,咱們還但願能後期增長類型,因而就誕生了任意類型。給interface添加一個任意屬性的方式以下:
interface person {
name: string;
[propName: string]: string;
}
複製代碼
這樣咱們就能夠給一個person類型的對象添加值爲string的屬性,讓這個對象能夠變大。
這裏須要注意一個很關鍵的問題:當接口中存在任意屬性時,其餘的全部屬性都必須是任意屬性的子集!,以下所示的接口定義就是錯誤的:
interface person { name: string; age?: number; [propName: string]: string; } 複製代碼
上面的可選屬性age的變量類型是number,不是任意屬性string的子集,因此編譯的時候就會報錯。
咱們能夠給interface聲明一個只讀屬性或方法,只能在初始化變量的時候賦值而不容許後續的修改,只要在屬性或方法前面加上一個readonly
關鍵詞,以下所示:
interface person {
readonly id: number;
name: string;
}
複製代碼
有了通常對象的類型,還剩下一些特殊的對象的類型:數組與函數。
數組的類型有多種種定義方式。
第一種是最簡單也最直觀的定義方式,直接在元素類型後面加上一對[]
,以下所示:
let arr: number[];
複製代碼
這就定義了arr
爲一個number數組。
既然數組是一種特殊的對象,天然也就可使用對象的類型定義方式:interface,具體以下所示:
interface NumberArray {
[index: number]: number;
}
let arr: NumberArray;
複製代碼
上面定義的NumberArray就是一種鍵爲數字,值也爲數字的對象,也就是數字數組。
元組
弱類型的js容許在一個數組中保存不一樣類型的數據,元組就是一種混雜類型的數組:
let mike: [string, number] = ["boy", 12]; 複製代碼
函數的聲明方式主要有兩種方法:函數聲明與函數表達式。這兩種寫法的類型定義方式是不同的。
函數聲明使用function關鍵字來聲明一個函數:
function sum(x, y) {
return x + y;
}
複製代碼
函數聲明的ts類型定義是比較簡單的:
function sum(x: number, y: number): number {
return x + y;
}
複製代碼
比較直觀也很好理解,只須要記住函數的輸出在形參的括號後面接冒號定義,別的都很直白。
函數表達式使用let或const(實際上幾乎都是const)來聲明一個匿名函數並賦值給一個變量:
const sum = function(x, y) {
return x + y;
}
複製代碼
函數表達式的類型定義是極其複雜的,以下所示:
const sum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
複製代碼
不只須要在匿名函數這裏聲明輸入輸出的變量類型,還須要聲明保存這個函數的指針的類型,並且這個類型寫起來極其複雜,使用了與箭頭函數同樣的=>
操做符,這種寫法讓人討厭的地方在於感受須要重複寫一遍類型定義。
函數也是特殊對象,可使用接口來定義輸入輸出類型,這樣就意味着,咱們會指定一個變量,它只能被賦值某個樣子的函數:
interface sum {
(x: number, y: number): number;
}
let mySum: sum;
複製代碼
上面的接口定義與函數聲明使用的定義很相像,都是一個圓括號包住輸入並分別定義類型,而後在括號外使用一個冒號定義輸出的類型。
上面的幾種定義方法其實在實際項目中都不多使用,由於在箭頭函數出現之後幾乎統治了世界。箭頭函數的類型定義一般以下:
const add = (a: number, b: number) => {
return a + b;
}
複製代碼
由於ts存在一個內部的類型推斷機制,全部咱們不須要對函數輸出進行類型定義,只定義輸入的類型,輸出的類型系統會自動推斷出來。
而當咱們把函數傳給參數的時候,給參數的定義則與函數表達式十分類似:
const add: (a: number, b: number) => number;
複製代碼
在聯合類型中,還沒被賦值的變量只能使用類型公共的屬性與方法,而使用類型斷言則可使用聯合類型中某個類型的屬性與方法:
let something: string | number;
something.length; // 會報錯
(<string>something).length; // 不會報錯,注意類型與斷言要用括號包起來當成一個變量使用
複製代碼
類型斷言的使用方法就是在變量前面使用尖括號斷言一個類型。若是沒有類型斷言,由於something的類型爲string與number的聯合類型,因此不能訪問只有string纔有的length屬性,可是經過類型斷言就能夠訪問了。
類型斷言的做用就是明確告訴ts這個變量的類型。類型斷言還有另一種書寫形式:
<string>something
===
something as string
複製代碼
關於類型斷言,須要記住它的通項:
as
關鍵字接類型是斷言類型斷言很關鍵的一點就是不要與泛型搞混。
類型別名使用type
操做符。
類型別名的做用就是用一個簡短的變量來存儲一長傳的類型定義。類型與類型之間也是可使用運算符執行邏輯運算的,好比聯合類型的|
操做符。下面是一個使用類型別名的例子:
type numstr = number | string;
let a: numstr = 2;
a = "tom";
複製代碼
在函數表達式的類型中,咱們使用了一串很複雜的東西來表示一個函數變量的類型:
let sum: (x: number, y: number) => number;
複製代碼
咱們可使用一個別名來簡化它:
type sumFun = (x: number, y: number) => number;
let sum: sumFun;
複製代碼
類型別名能夠理解成聲明一個類型變量的方式,使用一個變量來保存一個具體的類型。不要把類型別名type
與接口interface
搞混。
字符串字面量也使用type
操做符。
字符串字面量的做用就是把一個變量的類型限定爲某幾個特定的字符串,一個類型爲string的變量能夠保存任何字符串,可是字符串字面量類型只能保存特定的字符串中的一個。
type EventNames = 'click' | 'scroll' | 'mousemove';
let event: EventNames = 'click'
let event: EventNames = 'click2' // 會報錯
複製代碼
咱們已經知道了最基本的7個類型:
以及由他們組成的一些高級類型:
咱們可能會遇到一個問題:
若是咱們給每一個數據類型分別寫一個函數,那麼顯然浪費了不少資源來作重複的工做。若是把數據類型寫成any,則沒法保證輸出數組的元素與輸入類型相同。所以咱們但願,能有個方式告訴這個函數,咱們須要它把輸入輸出限制成什麼類型。這就是泛型的做用。
好比上面提到的例子,使用泛型來實現的代碼以下:
function createArr<T> (x: T, y: T): T[] {
return [x, y];
}
複製代碼
泛型的使用至關於給了函數另一套輸入參數,能夠輸入一些類型變量,並在函數中使用。上面的T只是一個示例,它只是一個泛型的形參,能夠與其餘形參同樣寫成任何知足要求的樣子。