TypeScript 终极初学者指南
大家好,我是若川。持續(xù)組織了8個(gè)月源碼共讀活動(dòng),感興趣的可以點(diǎn)此加我微信 ruochuan12?參與,每周大家一起學(xué)習(xí)200行左右的源碼,共同進(jìn)步。同時(shí)極力推薦訂閱我寫的《學(xué)習(xí)源碼整體架構(gòu)系列》?包含20余篇源碼文章。歷史面試系列
在過去的幾年里 TypeScript 變得越來越流行,現(xiàn)在許多工作都要求開發(fā)人員了解 TypeScript,各大廠的大型項(xiàng)目基本都要求使用 TypeScript 編寫。
如果你已經(jīng)對(duì) JavaScript 很熟了, TypeScript 基本上也能快速上手,下面是我整理的一些初學(xué)者必備的一些知識(shí)點(diǎn),如果你已經(jīng)是個(gè) TS 高手了,可以期待我后續(xù)的文章了~
Typescript 簡(jiǎn)介
據(jù)官方描述:TypeScript 是 JavaScript 的超集,這意味著它可以完成 JavaScript 所做的所有事情,而且額外附帶了一些能力。
JavaScript 本身是一種動(dòng)態(tài)類型語言,這意味著變量可以改變類型。使用 TypeScript 的主要原因是就是為了給 JavaScript 添加靜態(tài)類型。靜態(tài)類型意味著變量的類型在程序中的任何時(shí)候都不能改變。它可以防止很多bug !
Typescript 值得學(xué)嗎?
下面是學(xué)習(xí) Typescript 的幾個(gè)理由:
研究表明,TypeScript 可以發(fā)現(xiàn) 15% 的常見 bug。
TypeScript 可以讓代碼的可讀性更好,你可以更好的理解代碼是在做什么。
TypeScript 可以你申請(qǐng)到更多好工作。
學(xué)習(xí) TypeScript 可以使你對(duì) JavaScript 有更好的理解和新的視角。
當(dāng)然,使用 Typescript 也有一些缺點(diǎn):
TypeScript 的編寫時(shí)間比 JavaScript 要長(zhǎng),因?yàn)槟惚仨氁付愋?#xff0c;對(duì)于一些較小的獨(dú)立項(xiàng)目,可能不值使用。
TypeScript 需要編譯,項(xiàng)目越大消耗時(shí)間越長(zhǎng)。
但是,相比于提前發(fā)現(xiàn)更多的 bug,花更長(zhǎng)的時(shí)間也是值得的。
TypeScript 中的類型
原始類型
在 JavaScript 中,有 7 種原始類型:
string
number
bigint
boolean
undefined
null
symbol
原始類型都是不可變的,你可以為原始類型的變量重新分配一個(gè)新值,但不能像更改對(duì)象、數(shù)組和函數(shù)一樣更改它的值。可以看下面的例子:
let?name?=?'ConardLi'; name.toLowerCase(); console.log(name);?//?ConardLi?-?字符串的方法并沒有改變字符串本身let?arr?=?[1,?3,?5,?7]; arr.pop(); console.log(arr);?//?[1,?3,?5]?-?數(shù)組的方法改變了數(shù)組回到 TypeScript ,我們可以在聲明一個(gè)變量之后設(shè)置我們想要添加的類型 :type (我們一般稱之為“類型注釋”或“類型簽名”):
let?id:?number?=?5; let?firstname:?string?=?'ConardLi'; let?hasDog:?boolean?=?true;let?unit:?number;?//?聲明變量而不賦值 unit?=?5;但是,如果變量有默認(rèn)值的話,一般我們也不需要顯式聲明類型,TypeScript ?會(huì)自動(dòng)推斷變量的類型(類型推斷):
let?id?=?5;?//?number?類型 let?firstname?=?'ConardLi';?//?string?類型 let?hasDog?=?true;?//?boolean?類型hasDog?=?'yes';?//?ERROR我們還可以將變量設(shè)置為聯(lián)合類型(聯(lián)合類型是可以分配多個(gè)類型的變量):
let?age:?string?|?number; age?=?17; age?=?'17';TypeScript 中的數(shù)組
在 TypeScript 中,你可以定義數(shù)組包含的數(shù)據(jù)類型:
let?ids:?number[]?=?[1,?2,?3,?4,?5];?//?只能包含?number let?names:?string[]?=?['ConardLi',?'Tom',?'Jerry'];?//?只能包含?string let?options:?boolean[]?=?[true,?false,?false];?只能包含?true?false let?books:?object[]?=?[{?name:?'Tom',?animal:?'cat'?},{?name:?'Jerry',?animal:?'mouse'?}, ];?//?只能包含對(duì)象 let?arr:?any[]?=?['hello',?1,?true];?//?啥都行,回到了?JSids.push(6); ids.push('7');?//?ERROR:?Argument?of?type?'string'?is?not?assignable?to?parameter?of?type?'number'.你也可以使用聯(lián)合類型來定義包含多種類型的數(shù)組:
let?person:?(string?|?number?|?boolean)[]?=?['ConardLi',?1,?true]; person[0]?=?100; person[1]?=?{name:?'ConardLi'}?//?Error?-?person?array?can't?contain?objects如果數(shù)組有默認(rèn)值, TypeScript 同樣也會(huì)進(jìn)行類型推斷:
let?person?=?['ConardLi',?1,?true];?//?和上面的例子一樣 person[0]?=?100; person[1]?=?{?name:?'ConardLi'?};?//?Error?-?person?array?can't?contain?objectsTypeScript 中可以定義一種特殊類型的數(shù)組:元組(Tuple)。元組是具有固定大小和已知數(shù)據(jù)類型的數(shù)組,它比常規(guī)數(shù)組更嚴(yán)格。
let?person:?[string,?number,?boolean]?=?['ConardLi',?1,?true]; person[0]?=?17;?//?Error?-?Value?at?index?0?can?only?be?a?stringTypeScript 中的對(duì)象
TypeScript 中的對(duì)象必須擁有所有正確的屬性和值類型:
//?使用特定的對(duì)象類型注釋聲明一個(gè)名為?person?的變量 let?person:?{name:?string;age:?number;isProgrammer:?boolean; };//?給?person?分配一個(gè)具有所有必要屬性和值類型的對(duì)象 person?=?{name:?'ConardLi',age:?17,isProgrammer:?true, };person.age?=?'17';?//?ERROR:?should?be?a?numberperson?=?{name:?'Tom',age:?3, };? //?ERROR:?missing?the?isProgrammer?property在定義對(duì)象的類型時(shí),我們通常會(huì)使用 interface。如果我們需要檢查多個(gè)對(duì)象是否具有相同的特定屬性和值類型時(shí),是很有用的:
interface?Person?{name:?string;age:?number;isProgrammer:?boolean; }let?person1:?Person?=?{name:?'ConardLi',age:?17,isProgrammer:?true, };let?person2:?Person?=?{name:?'Tom',age:?3,isProgrammer:?false, };我們還可以用函數(shù)的類型簽名聲明一個(gè)函數(shù)屬性,通用函數(shù)(sayHi)和箭頭函數(shù)(sayBye)都可以聲明:
interface?Animal?{eat(name:?string):?string;speak:?(name:?string)?=>?string; }let?tom:?Animal?=?{eat:?function?(name:?string)?{return?`eat?${name}`;},speak:?(name:?string)?=>?`speak?${name}`, };console.log(tom.eat('Jerry')); console.log(tom.speak('哈哈哈'));需要注意的是,雖然 eat、speak 分別是用普通函數(shù)和箭頭函數(shù)聲明的,但是它們具體是什么樣的函數(shù)類型都可以,Typescript 是不關(guān)心這些的。
TypeScript 中的函數(shù)
我們可以定義函數(shù)參數(shù)和返回值的類型:
//?定義一個(gè)名為?circle?的函數(shù),它接受一個(gè)類型為?number?的直徑變量,并返回一個(gè)字符串 function?circle(diam:?number):?string?{return?'圓的周長(zhǎng)為:'?+?Math.PI?*?diam; }console.log(circle(10));?//?圓的周長(zhǎng)為:31.41592653589793ES6 箭頭函數(shù)的寫法:
const?circle?=?(diam:?number):?string?=>?{return?'圓的周長(zhǎng)為:'?+?Math.PI?*?diam; };我們沒必要明確聲明 circle 是一個(gè)函數(shù),TypeScript 會(huì)進(jìn)行類型推斷。TypeScript 還會(huì)推斷函數(shù)的返回類型,但是如果函數(shù)體比較復(fù)雜,還是建議清晰的顯式聲明返回類型。
我們可以在參數(shù)后添加一個(gè)?,表示它為可選參數(shù);另外參數(shù)的類型也可以是一個(gè)聯(lián)合類型:
const?add?=?(a:?number,?b:?number,?c?:?number?|?string)?=>?{console.log(c);return?a?+?b; };console.log(add(5,?4,?'可以是?number、string,也可以為空'));如果函數(shù)沒有返回值,在 TS 里表示為返回 void,你也不需要顯式聲明,TS 一樣可以進(jìn)行類型推斷:
const?log?=?(msg:?string):?void?=>?{console.log('打印一些內(nèi)容:?'?+?msg); };any 類型
使 any 類型,我們基本上可以將 TypeScript 恢復(fù)為 JavaScript:
let?name:?any?=?'ConardLi'; name?=?17; name?=?{?age:?17?};如果代碼里使用了大量的 any,那 TypeScript 也就失去了意義,所以我們應(yīng)該盡量避免使用 any 。
DOM 和類型轉(zhuǎn)換
TypeScript 沒辦法像 JavaScript 那樣訪問 DOM。這意味著每當(dāng)我們嘗試訪問 DOM 元素時(shí),TypeScript 都無法確定它們是否真的存在。
const?link?=?document.querySelector('a');console.log(link.href);?//?ERROR:?Object?is?possibly?'null'.?TypeScript?can't?be?sure?the?anchor?tag?exists,?as?it?can't?access?the?DOM使用非空斷言運(yùn)算符 (!),我們可以明確地告訴編譯器一個(gè)表達(dá)式的值不是 null 或 undefined。當(dāng)編譯器無法準(zhǔn)確地進(jìn)行類型推斷時(shí),這可能很有用:
//?我們明確告訴?TS?a?標(biāo)簽肯定存在 const?link?=?document.querySelector('a')!;console.log(link.href);?//?conardli.top這里我們沒必要聲明 link 變量的類型。這是因?yàn)?TypeScript 可以通過類型推斷確認(rèn)它的類型為 HTMLAnchorElement。
但是如果我們需要通過 class 或 id 來選擇一個(gè) DOM 元素呢?這時(shí) TypeScript 就沒辦法推斷類型了:
const?form?=?document.getElementById('signup-form');console.log(form.method); //?ERROR:?Object?is?possibly?'null'. //?ERROR:?Property?'method'?does?not?exist?on?type?'HTMLElement'.我們需要告訴 TypeScript form 確定是存在的,并且我們知道它的類型是 ?HTMLFormElement。我們可以通過類型轉(zhuǎn)換來做到這一點(diǎn):
const?form?=?document.getElementById('signup-form')?as?HTMLFormElement;console.log(form.method);?//?postTypeScript 還內(nèi)置了一個(gè) Event 對(duì)象。如果我們?cè)诒韱沃刑砑右粋€(gè) submit 的事件偵聽器,TypeScript 可以自動(dòng)幫我們推斷類型錯(cuò)誤:
const?form?=?document.getElementById('signup-form')?as?HTMLFormElement;form.addEventListener('submit',?(e:?Event)?=>?{e.preventDefault();?//?阻止頁面刷新console.log(e.tarrget);?//?ERROR:?Property?'tarrget'?does?not?exist?on?type?'Event'.?Did?you?mean?'target'? });TypeScript 中的類
我們可以定義類中每條數(shù)據(jù)的類型:
class?Person?{name:?string;isCool:?boolean;age:?number;constructor(n:?string,?c:?boolean,?a:?number)?{this.name?=?n;this.isCool?=?c;this.age?=?a;}sayHello()?{return?`Hi,我是?${this.name}?,我今年?${this.age}?歲了`;} }const?person1?=?new?Person('ConardLi',?true,?17); const?person2?=?new?Person('Jerry',?'yes',?20);?//?ERROR:?Argument?of?type?'string'?is?not?assignable?to?parameter?of?type?'boolean'.console.log(person1.sayHello());?//?Hi,?我是?ConardLi,我今年?17?歲了我們可以創(chuàng)建一個(gè)僅包含從 Person 構(gòu)造的對(duì)象數(shù)組:
let?People:?Person[]?=?[person1,?person2];我們可以給類的屬性添加訪問修飾符,TypeScript 還提供了一個(gè)新的 readonly 訪問修飾符。
class?Person?{readonly?name:?string;?//?不可以變的private?isCool:?boolean;?//?類的私有屬性、外部訪問不到protected?email:?string;?//?只能從這個(gè)類和子類中進(jìn)行訪問和修改public?age:?number;?//?任何地方都可以訪問和修改constructor(n:?string,?c:?boolean,?a:?number)?{this.name?=?n;this.isCool?=?c;this.age?=?a;}sayHello()?{return?`Hi,我是?${this.name}?,我今年?${this.age}?歲了`;} }const?person1?=?new?Person('ConardLi',?true,?'conard@xx.com',?17); console.log(person1.name);?//?ConardLi person1.name?=?'Jerry';?//?Error:?read?only我們可以通過下面的寫法,屬性會(huì)在構(gòu)造函數(shù)中自動(dòng)分配,我們類會(huì)更加簡(jiǎn)潔:
class?Person?{constructor(readonly?name:?string,private?isCool:?boolean,protected?email:?string,public?age:?number)?{} }如果我們省略訪問修飾符,默認(rèn)情況下屬性都是 public,另外和 JavaScript 一樣,類也是可以 extends 的。
TypeScript 中的接口
接口定義了對(duì)象的外觀:
interface?Person?{name:?string;age:?number; }function?sayHi(person:?Person)?{console.log(`Hi?${person.name}`); }sayHi({name:?'ConardLi',age:?17, });?//?Hi?ConardLi你還可以使用類型別名定義對(duì)象類型:
type?Person?=?{name:?string;age:?number; };或者可以直接匿名定義對(duì)象類型:
function?sayHi(person:?{?name:?string;?age:?number?})?{console.log(`Hi?${person.name}`); }interface 和 type 非常相似,很多情況下它倆可以隨便用。比如它們兩個(gè)都可以擴(kuò)展:
擴(kuò)展 interface:
interface?Animal?{name:?string }interface?Bear?extends?Animal?{honey:?boolean }const?bear:?Bear?=?{name:?"Winnie",honey:?true, }擴(kuò)展 type:
type?Animal?=?{name:?string }type?Bear?=?Animal?&?{honey:?boolean }const?bear:?Bear?=?{name:?"Winnie",honey:?true, }但是有個(gè)比較明顯的區(qū)別,interface 是可以自動(dòng)合并類型的,但是 type 不支持:
interface?Animal?{name:?string }interface?Animal?{tail:?boolean }const?dog:?Animal?=?{name:?"Tom",tail:?true, }類型別名在創(chuàng)建后無法更改:
type?Animal?=?{name:?string }type?Animal?=?{tail:?boolean } //?ERROR:?Duplicate?identifier?'Animal'.一般來說,當(dāng)你不知道用啥的時(shí)候,默認(rèn)就用 interface 就行,直到 interface 滿足不了我們的需求的時(shí)候再用 type。
類的 interface
我們可以通過實(shí)現(xiàn)一個(gè)接口來告訴一個(gè)類它必須包含某些屬性和方法:
interface?HasFormatter?{format():?string; }class?Person?implements?HasFormatter?{constructor(public?username:?string,?protected?password:?string)?{}format()?{return?this.username.toLocaleLowerCase();} }let?person1:?HasFormatter; let?person2:?HasFormatter;person1?=?new?Person('ConardLi',?'admin123'); person2?=?new?Person('Tom',?'admin123');console.log(person1.format());?//?conardli確保 people 是一個(gè)實(shí)現(xiàn) HasFormatter 的對(duì)象數(shù)組(確保每 people 都有 format 方法):
let?people:?HasFormatter[]?=?[]; people.push(person1); people.push(person2);泛型
泛型可以讓我們創(chuàng)建一個(gè)可以在多種類型上工作的組件,它能夠支持當(dāng)前的數(shù)據(jù)類型,同時(shí)也能支持未來的數(shù)據(jù)類型,這大大提升了組件的可重用性。我們來看下面這個(gè)例子:
addID 函數(shù)接受一個(gè)任意對(duì)象,并返回一個(gè)新對(duì)象,其中包含傳入對(duì)象的所有屬性和值,以及一個(gè) 0 到 1000 之間隨機(jī)的 id 屬性。
const?addID?=?(obj:?object)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?}; };let?person1?=?addID({?name:?'John',?age:?40?});console.log(person1.id);?//?271 console.log(person1.name);?//?ERROR:?Property?'name'?does?not?exist?on?type?'{?id:?number;?}'.當(dāng)我們嘗試訪問 name 屬性時(shí),TypeScript 會(huì)出錯(cuò)。這是因?yàn)楫?dāng)我們將一個(gè)對(duì)象傳遞給 addID 時(shí),我們并沒有指定這個(gè)對(duì)象應(yīng)該有什么屬性 —— 所以 TypeScript 不知道這個(gè)對(duì)象有什么屬性。因此,TypeScript 知道的唯一屬性返回對(duì)象的 id。
那么,我們?cè)趺磳⑷我鈱?duì)象傳遞給 addID,而且仍然可以告訴 TypeScript 該對(duì)象具有哪些屬性和值?這種場(chǎng)景就可以使用泛型了, <T> – T 被稱為類型參數(shù):
//?<T>?只是一種編寫習(xí)慣?-?我們也可以用?<X>?或?<A> const?addID?=?<T>(obj:?T)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?}; };這是啥意思呢?現(xiàn)在當(dāng)我們?cè)賹⒁粋€(gè)對(duì)象傳遞給 addID 時(shí),我們已經(jīng)告訴 TypeScript 來捕獲它的類型了 —— 所以 T 就變成了我們傳入的任何類型。addID 現(xiàn)在會(huì)知道我們傳入的對(duì)象上有哪些屬性。
但是,現(xiàn)在有另一個(gè)問題:任何東西都可以傳入 addID ,TypeScript 將捕獲類型而且并不會(huì)報(bào)告問題:
let?person1?=?addID({?name:?'ConardLi',?age:?17?}); let?person2?=?addID('Jerry');?//?傳遞字符串也沒問題console.log(person1.id);?//?188 console.log(person1.name);?//?ConardLiconsole.log(person2.id); console.log(person2.name);?//?ERROR:?Property?'name'?does?not?exist?on?type?'"Jerry"?&?{?id:?number;?}'.當(dāng)我們傳入一個(gè)字符串時(shí),TypeScript 沒有發(fā)現(xiàn)任何問題。只有我們嘗試訪問 name 屬性時(shí)才會(huì)報(bào)告錯(cuò)誤。所以,我們需要一個(gè)約束:我們需要通過將泛型類型 T 作為 object 的擴(kuò)展,來告訴 TypeScript 只能接受對(duì)象:
const?addID?=?<T?extends?object>(obj:?T)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?}; };let?person1?=?addID({?name:?'John',?age:?40?}); let?person2?=?addID('Jerry');?//?ERROR:?Argument?of?type?'string'?is?not?assignable?to?parameter?of?type?'object'.錯(cuò)誤馬上就被捕獲了,完美…… 好吧,也不完全是。在 JavaScript 中,數(shù)組也是對(duì)象,所以我們?nèi)匀豢梢酝ㄟ^傳入數(shù)組來逃避類型檢查:
let?person2?=?addID(['ConardLi',?17]);?//?傳遞數(shù)組沒問題console.log(person2.id);?//?188 console.log(person2.name);?//?Error:?Property?'name'?does?not?exist?on?type?'(string?|?number)[]?&?{?id:?number;?}'.要解決這個(gè)問題,我們可以這樣說:object 參數(shù)應(yīng)該有一個(gè)帶有字符串值的 name 屬性:
const?addID?=?<T?extends?{?name:?string?}>(obj:?T)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?}; };let?person2?=?addID(['ConardLi',?17]);?//?ERROR:?argument?should?have?a?name?property?with?string?value泛型允許在參數(shù)和返回類型提前未知的組件中具有類型安全。
在 TypeScript 中,泛型用于描述兩個(gè)值之間的對(duì)應(yīng)關(guān)系。在上面的例子中,返回類型與輸入類型有關(guān)。我們用一個(gè)泛型來描述對(duì)應(yīng)關(guān)系。
另一個(gè)例子:如果需要接受多個(gè)類型的函數(shù),最好使用泛型而不是 any 。下面展示了使用 any 的問題:
function?logLength(a:?any)?{console.log(a.length);?//?No?errorreturn?a; }let?hello?=?'Hello?world'; logLength(hello);?//?11let?howMany?=?8; logLength(howMany);?//?undefined?(but?no?TypeScript?error?-?surely?we?want?TypeScript?to?tell?us?we've?tried?to?access?a?length?property?on?a?number!)我們可以嘗試使用泛型:
function?logLength<T>(a:?T)?{console.log(a.length);?//?ERROR:?TypeScript?isn't?certain?that?`a`?is?a?value?with?a?length?propertyreturn?a; }好,至少我們現(xiàn)在得到了一些反饋,可以幫助我們持續(xù)改進(jìn)我們的代碼。
解決方案:使用一個(gè)泛型來擴(kuò)展一個(gè)接口,確保傳入的每個(gè)參數(shù)都有一個(gè) length 屬性:
interface?hasLength?{length:?number; }function?logLength<T?extends?hasLength>(a:?T)?{console.log(a.length);return?a; }let?hello?=?'Hello?world'; logLength(hello);?//?11let?howMany?=?8; logLength(howMany);?//?Error:?numbers?don't?have?length?properties我們也可以編寫這樣一個(gè)函數(shù),它的參數(shù)是一個(gè)元素?cái)?shù)組,這些元素都有一個(gè) length 屬性:
interface?hasLength?{length:?number; }function?logLengths<T?extends?hasLength>(a:?T[])?{a.forEach((element)?=>?{console.log(element.length);}); }let?arr?=?['This?string?has?a?length?prop',['This',?'arr',?'has',?'length'],{?material:?'plastic',?length:?17?}, ];logLengths(arr); //?29 //?4 //?30泛型是 TypeScript 的一個(gè)很棒的特性!
泛型接口
當(dāng)我們不知道對(duì)象中的某個(gè)值是什么類型時(shí),可以使用泛型來傳遞該類型:
//?The?type,?T,?will?be?passed?in interface?Person<T>?{name:?string;age:?number;documents:?T; }//?We?have?to?pass?in?the?type?of?`documents`?-?an?array?of?strings?in?this?case const?person1:?Person<string[]>?=?{name:?'ConardLi',age:?17,documents:?['passport',?'bank?statement',?'visa'], };//?Again,?we?implement?the?`Person`?interface,?and?pass?in?the?type?for?documents?-?in?this?case?a?string const?person2:?Person<string>?=?{name:?'Tom',age:?20,documents:?'passport,?P45', };枚舉
枚舉是 TypeScript 給 JavaScript 帶來的一個(gè)特殊特性。枚舉允許我們定義或聲明一組相關(guān)值,可以是數(shù)字或字符串,作為一組命名常量。
enum?ResourceType?{BOOK,AUTHOR,FILM,DIRECTOR,PERSON, }console.log(ResourceType.BOOK);?//?0 console.log(ResourceType.AUTHOR);?//?1//?從?1?開始 enum?ResourceType?{BOOK?=?1,AUTHOR,FILM,DIRECTOR,PERSON, }console.log(ResourceType.BOOK);?//?1 console.log(ResourceType.AUTHOR);?//?2默認(rèn)情況下,枚舉是基于數(shù)字的 — 它們將字符串值存儲(chǔ)為數(shù)字。但它們也可以是字符串:
enum?Direction?{Up?=?'Up',Right?=?'Right',Down?=?'Down',Left?=?'Left', }console.log(Direction.Right);?//?Right console.log(Direction.Down);?//?Down當(dāng)我們有一組相關(guān)的常量時(shí),枚舉就可以派上用場(chǎng)了。例如,與在代碼中使用非描述性數(shù)字不同,枚舉通過描述性常量使代碼更具可讀性。
枚舉還可以防止錯(cuò)誤,因?yàn)楫?dāng)你輸入枚舉的名稱時(shí),智能提示將彈出可能選擇的選項(xiàng)列表。
TypeScript 嚴(yán)格模式
建議在 tsconfig.json 中啟用所有嚴(yán)格的類型檢查操作文件。這可能會(huì)導(dǎo)致 TypeScript 報(bào)告更多的錯(cuò)誤,但也更有助于幫你提前發(fā)現(xiàn)發(fā)現(xiàn)程序中更多的 bug。
//?tsconfig.json"strict":?true嚴(yán)格模式實(shí)際上就意味著:禁止隱式 any 和 嚴(yán)格的空檢查。
禁止隱式 any
在下面的函數(shù)中,TypeScript 已經(jīng)推斷出參數(shù) a 是 any 類型的。當(dāng)我們向該函數(shù)傳遞一個(gè)數(shù)字,并嘗試打印一個(gè) name 屬性時(shí),沒有報(bào)錯(cuò):
function?logName(a)?{//?No?error??console.log(a.name); }logName(97);打開 noImplicitAny 選項(xiàng)后,如果我們沒有顯式地聲明 a 的類型,TypeScript 將立即標(biāo)記一個(gè)錯(cuò)誤:
//?ERROR:?Parameter?'a'?implicitly?has?an?'any'?type. function?logName(a)?{console.log(a.name); }嚴(yán)格的空檢查
當(dāng) strictNullChecks 選項(xiàng)為 false 時(shí),TypeScript 實(shí)際上會(huì)忽略 null 和 undefined。這可能會(huì)在運(yùn)行時(shí)導(dǎo)致意外錯(cuò)誤。
當(dāng) strictNullChecks 設(shè)置為 true 時(shí),null 和 undefined 有它們自己的類型,如果你將它們分配給一個(gè)期望具體值(例如,字符串)的變量,則會(huì)得到一個(gè)類型錯(cuò)誤。
let?whoSangThis:?string?=?getSong();const?singles?=?[{?song:?'touch?of?grey',?artist:?'grateful?dead'?},{?song:?'paint?it?black',?artist:?'rolling?stones'?}, ];const?single?=?singles.find((s)?=>?s.song?===?whoSangThis);console.log(single.artist);singles.find 并不能保證它一定能找到這首歌 — 但是我們已經(jīng)編寫了下面的代碼,好像它肯定能找到一樣。
通過將 strictNullChecks 設(shè)置為 true, TypeScript 將拋出一個(gè)錯(cuò)誤,因?yàn)樵趪L試使用它之前,我們沒有保證 single 一定存在:
const?getSong?=?()?=>?{return?'song'; };let?whoSangThis:?string?=?getSong();const?singles?=?[{?song:?'touch?of?grey',?artist:?'grateful?dead'?},{?song:?'paint?it?black',?artist:?'rolling?stones'?}, ];const?single?=?singles.find((s)?=>?s.song?===?whoSangThis);console.log(single.artist);?//?ERROR:?Object?is?possibly?'undefined'.TypeScript 基本上是告訴我們?cè)谑褂?single 之前要確保它存在。我們需要先檢查它是否為 null 或 undefined:
if?(single)?{console.log(single.artist);?//?rolling?stones }TypeScript 中的類型收窄
在 TypeScript 中,變量可以從不太精確的類型轉(zhuǎn)移到更精確的類型,這個(gè)過程稱為類型收窄。
下面是一個(gè)簡(jiǎn)單的例子,展示了當(dāng)我們使用帶有 typeof 的 if 語句時(shí),TypeScript 如何將不太特定的 string | number 縮小到更特定的類型:
function?addAnother(val:?string?|?number)?{if?(typeof?val?===?'string')?{//?ts?將?val?視為一個(gè)字符串return?val.concat('?'?+?val);}//?ts?知道?val?在這里是一個(gè)數(shù)字return?val?+?val; }console.log(addAnother('哈哈'));?//?哈哈?哈哈 console.log(addAnother(17));?//?34另一個(gè)例子:下面,我們定義了一個(gè)名為 allVehicles 的聯(lián)合類型,它可以是 Plane 或 Train 類型。
interface?Vehicle?{topSpeed:?number; }interface?Train?extends?Vehicle?{carriages:?number; }interface?Plane?extends?Vehicle?{wingSpan:?number; }type?PlaneOrTrain?=?Plane?|?Train;function?getSpeedRatio(v:?PlaneOrTrain)?{console.log(v.carriages);?//?ERROR:?'carriages'?doesn't?exist?on?type?'Plane' }由于 getSpeedRatio 函數(shù)處理了多種類型,我們需要一種方法來區(qū)分 v 是 Plane 還是 Train 。我們可以通過給這兩種類型一個(gè)共同的區(qū)別屬性來做到這一點(diǎn),它帶有一個(gè)字符串值:
interface?Train?extends?Vehicle?{type:?'Train';carriages:?number; }interface?Plane?extends?Vehicle?{type:?'Plane';wingSpan:?number; }type?PlaneOrTrain?=?Plane?|?Train;現(xiàn)在,TypeScript 可以縮小 v 的類型:
function?getSpeedRatio(v:?PlaneOrTrain)?{if?(v.type?===?'Train')?{return?v.topSpeed?/?v.carriages;}//?如果不是 Train,ts 知道它就是 Plane 了,聰明!return?v.topSpeed?/?v.wingSpan; }let?bigTrain:?Train?=?{type:?'Train',topSpeed:?100,carriages:?20, };console.log(getSpeedRatio(bigTrain));?//?5另外,我們還可以通過實(shí)現(xiàn)一個(gè)類型保護(hù)來解決這個(gè)問題,可以看看這篇文章:什么是鴨子🦆類型?
TypeScript & React
TypeScript 完全支持 React 和 JSX。這意味著我們可以將 TypeScript 與三個(gè)最常見的 React 框架一起使用:
create-react-app (https://create-react-app.dev/docs/adding-typescript/)
Gatsby (https://www.gatsbyjs.com/docs/how-to/custom-configuration/typescript/)
Next.js (https://nextjs.org/learn/excel/typescript)
如果你需要一個(gè)更自定義的 React-TypeScript 配置,你可以字節(jié)配置 Webpack 和 tsconfig.json。但是大多數(shù)情況下,一個(gè)框架就可以完成這項(xiàng)工作。
例如,要用 TypeScript 設(shè)置 create-react-app,只需運(yùn)行:
npx?create-react-app?my-app?--template?typescript#?oryarn?create?react-app?my-app?--template?typescript在 src 文件夾中,我們現(xiàn)在可以創(chuàng)建帶有 .ts (普通 TypeScript 文件)或 .tsx (帶有 React 的 TypeScript 文件)擴(kuò)展名的文件,并使用 TypeScript 編寫我們的組件。然后將其編譯成 public 文件夾中的 JavaScript 。
React props & TypeScript
Person 是一個(gè) React 組件,它接受一個(gè) props 對(duì)象,其中 name 應(yīng)該是一個(gè)字符串,age 是一個(gè)數(shù)字。
//?src/components/Person.tsx import?React?from?'react';const?Person:?React.FC<{name:?string;age:?number; }>?=?({?name,?age?})?=>?{return?(<div><div>{name}</div><div>{age}</div></div>); };export?default?Person;一般我們更喜歡用 interface 定義 props:
interface?Props?{name:?string;age:?number; }const?Person:?React.FC<Props>?=?({?name,?age?})?=>?{return?(<div><div>{name}</div><div>{age}</div></div>); };然后我們嘗試將組件導(dǎo)入到 App.tsx,如果我們沒有提供必要的 props,TypeScript 會(huì)報(bào)錯(cuò)。
import?React?from?'react'; import?Person?from?'./components/Person';const?App:?React.FC?=?()?=>?{return?(<div><Person?name='ConardLi'?age={17}?/></div>); };export?default?App;React hooks & TypeScript
useState()
我們可以用尖括號(hào)來聲明狀態(tài)變量的類型。如果我們省略了尖括號(hào),TypeScript 會(huì)默認(rèn)推斷 cash 是一個(gè)數(shù)字。因此,如果想讓它也為空,我們必須指定:
const?Person:?React.FC<Props>?=?({?name,?age?})?=>?{const?[cash,?setCash]?=?useState<number?|?null>(1);setCash(null);return?(<div><div>{name}</div><div>{age}</div></div>); };useRef()
useRef 返回一個(gè)可變對(duì)象,該對(duì)象在組件的生命周期內(nèi)都是持久的。我們可以告訴 TypeScript ? ref 對(duì)象應(yīng)該指向什么:
const?Person:?React.FC?=?()?=>?{//?Initialise?.current?property?to?nullconst?inputRef?=?useRef<HTMLInputElement>(null);return?(<div><input?type='text'?ref={inputRef}?/></div>); };參考
https://www.typescriptlang.org/docs/
https://react-typescript-cheatsheet.netlify.app/
https://www.freecodecamp.org/news/learn-typescript-beginners-guide
好了,這篇文章我們學(xué)習(xí)了一些 Typescript 的必備基礎(chǔ),有了這些知識(shí)你已經(jīng)可以應(yīng)付大部分 TS 的應(yīng)用場(chǎng)景了,后續(xù)我會(huì)出一些 TS 的高級(jí)技巧相關(guān)的文章,敬請(qǐng)期待吧 ~
·················?若川簡(jiǎn)介?·················
你好,我是若川,畢業(yè)于江西高校。現(xiàn)在是一名前端開發(fā)“工程師”。寫有《學(xué)習(xí)源碼整體架構(gòu)系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會(huì)寫一篇年度總結(jié),已經(jīng)堅(jiān)持寫了8年,點(diǎn)擊查看年度總結(jié)。
同時(shí),最近組織了源碼共讀活動(dòng),幫助3000+前端人學(xué)會(huì)看源碼。公眾號(hào)愿景:幫助5年內(nèi)前端人走向前列。
掃碼加我微信 ruochuan02、拉你進(jìn)源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進(jìn)群的可以加我微信 ruochuan12?進(jìn)群。分享、收藏、點(diǎn)贊、在看我的文章就是對(duì)我最大的支持~
總結(jié)
以上是生活随笔為你收集整理的TypeScript 终极初学者指南的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(2933):vue中的循环语句
- 下一篇: [html] 你有了解video的x5