全網(wǎng)最全的,最詳細(xì)的,最友好的 Typescript 新手教程
全網(wǎng)最全的,最詳細(xì)的,最友好的 Typescript 新手教程,拿走不謝,希望給個點贊,在看,轉(zhuǎn)發(fā),謝謝

本文翻譯自
TypeScript tutorial for beginners: who this guide is for
TypeScript新手教程:本指南是給誰看的
下面的指南是一個TypeScript教程,供有興趣學(xué)習(xí)更多TypeScript知識的JavaScript開發(fā)者使用。這意味著您需要對“普通的”JavaScript有足夠的了解,盡管我將在接下來的過程中為您提供一些基本的指導(dǎo)。
單詞TypeScript和“初學(xué)者”屬于同一個教程嗎?在寫這篇指南之前,我并不確定,但每天我都看到很多初學(xué)者對TypeScript感興趣。如果你決定這樣做,要意識到,在你早期的時候,同時學(xué)習(xí)TypeScript和JavaScript是很難的。但從長遠(yuǎn)來看,這是值得的。繼續(xù)前進!如果這是你的情況,歡迎你繼續(xù)閱讀。
在開始之前,請確保系統(tǒng)上安裝了最新版本的Node.js。
現(xiàn)在享受閱讀吧!
TypeScript初學(xué)者教程:什么是TypeScript?
官方網(wǎng)站上的定義是:“JavaScript的類型化超集”,但它假設(shè)你知道“超集”是什么,以及“類型化”是什么意思。為了簡單起見,你可以把TypeScript看作是JavaScript的“頂層”。
TypeScript是一個層,因為你可以在你的編輯器中編寫TypeScript代碼。編譯之后,所有TypeScript的東西都消失了,剩下的只是簡單的JavaScript。
如果編譯步驟的概念讓您感到困惑,請記住JavaScript已經(jīng)編譯并解釋過了。有一個JavaScript引擎讀取并執(zhí)行你的代碼。
但是JavaScript引擎不能讀取TypeScript代碼,所以任何TypeScript文件都要經(jīng)過“預(yù)翻譯”過程,也就是編譯。只有在第一個編譯步驟之后,才剩下純JavaScript代碼,可以在瀏覽器中運行。稍后你會看到TypeScript是如何編譯的。
現(xiàn)在我們要記住,TypeScript是一種特殊的JavaScript,但在瀏覽器中運行之前,它需要一個“轉(zhuǎn)換器”。
TypeScript新手教程:為什么是TypeScript?
一開始,你不會完全理解TypeScript為什么有意義,畢竟它在變成JavaScript代碼之前已經(jīng)被剝離了。你會問:“TypeScript有什么用?”這是個好問題,我的朋友。
實際上,只要它能捕獲代碼中嚴(yán)重和愚蠢的錯誤,您就會看到它的好處。更重要的是,您的代碼庫將變得結(jié)構(gòu)良好,并且?guī)缀跏亲晕臋n化的。您還將欣賞編輯器中改進的自動完成功能,但這只是一個不錯的副作用。
不管怎么說,Twitter或“orange網(wǎng)站”上時不時會彈出一個新帖子,說TypeScript沒用(TypeScript稅)或太尷尬。
街壘的兩邊幾乎都有游擊隊員。TypeScript有褒有貶,但重要的是,TypeScript是一個可靠的工具,把它放在你的工具帶上不會有什么壞處。
我的目標(biāo)是展示這個工具,并幫助你形成自己對TypeScript的看法。
初學(xué)者的TypeScript教程:設(shè)置TypeScript
設(shè)置?為什么如此?TypeScript不只是一種語言嗎?種。TypeScript還有一個二進制代碼,可以把TypeScript代碼編譯成JavaScript代碼。記住,瀏覽器并不理解TypeScript。那么,讓我們安裝二進制文件。在一個新的文件夾中創(chuàng)建一個新的節(jié)點項目:
mkdir typescript-tutorial && cd $_
npm init -y
然后用以下方式安裝TypeScript:
npm i typescript --save-dev
接下來,配置一個節(jié)點腳本,這樣我們就可以輕松地運行TypeScript編譯器了:
"scripts": {
"tsc": "tsc"
},
tsc代表TypeScript編譯器,當(dāng)編譯器運行時,它會尋找一個名為tsconfig的文件。json在項目文件夾中。讓我們?yōu)門ypeScript生成一個配置文件:
npm run tsc -- --init
如果一切順利,您將得到“消息TS6071:成功創(chuàng)建tsconfig。您將在項目文件夾中看到新文件?,F(xiàn)在,保持冷靜。tsconfig。json是一個可怕的配置文件。你不需要知道它的每一個要點。在下一節(jié)中,您將看到入門的相關(guān)部分。
TypeScript新手教程:配置TypeScript編譯器
初始化一個git repo并提交原始tsconfig是一個好主意。在接觸文件之前。我們將只保留一些配置選項,并刪除其他所有選項。稍后,你可能會想要將你的版本與原始版本進行比較。為了開始打開tsconfig.json和替換所有的原始內(nèi)容與以下:
{
"compilerOptions": {
"target": "es5",
"strict": true
}
}
保存并關(guān)閉該文件。首先,你可能想知道tsconfig是什么。json。TypeScript編譯器和任何支持TypeScript的代碼編輯器都會讀取這個配置文件。
TypeScript編譯成“普通的”JavaScript。關(guān)鍵目標(biāo)確定所需的JavaScript版本ES5(或最新版本)。
這取決于tsconfig的“嚴(yán)格程度”。如果您沒有將適當(dāng)?shù)念愋妥⑨屘砑拥酱a中,編譯器和編輯器將遵守此規(guī)則(稍后將詳細(xì)介紹這一點)。
當(dāng)strict設(shè)置為true時,TypeScript會在你的代碼中強制執(zhí)行最大級別的類型檢查:
noImplicitAny true:當(dāng)變量沒有定義類型時,TypeScript會報錯
always sstrict true:嚴(yán)格模式是JavaScript的一種安全機制,它可以防止意外全局變量,默認(rèn)此綁定,等等。當(dāng)always sstrict設(shè)置為true時,TypeScript會在每個JavaScript文件的最頂部發(fā)出"use strict"。
還有更多可用的配置選項。隨著時間的推移,你會學(xué)到更多,目前以上兩個選項是你開始需要知道的一切。但"any"是什么意思?
關(guān)于types的幾個單詞
現(xiàn)在你應(yīng)該知道TypeScript是做什么的了。一切都圍繞著類型展開。它們不是典型的JavaScript“類型”,如String、Object、Boolean。TypeScript會自己添加更多類型,就像any(或更多)一樣。
any是一個“松散的”TypeScript類型。這意味著:這個變量可以是任何類型:字符串,布爾值,對象,真的,我不在乎。這實際上就像根本沒有類型檢查一樣。當(dāng)strict設(shè)置為true時,你就會對TypeScript說“不要在我的代碼中產(chǎn)生歧義”。
出于這個原因,我建議對TypeScript保持最大程度的嚴(yán)格,即使在一開始修復(fù)所有錯誤會比較困難?,F(xiàn)在我們幾乎已經(jīng)準(zhǔn)備好看到TypeScript的運行了!
初學(xué)者的TypeScript教程:TypeScript的作用
一切都以合法的(顯然)JavaScript函數(shù)filterByTerm開頭。在你的項目文件夾中創(chuàng)建一個名為filterByTerm.js的新文件,并將以下代碼復(fù)制到其中:
function filterByTerm(input, searchTerm) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("inputArr cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
filterByTerm("input string", "java");
如果你現(xiàn)在不明白其中的邏輯,也不要擔(dān)心。在幾行之后,我們來看看這個函數(shù)的參數(shù)以及它們是如何使用的。僅通過查看代碼,您就應(yīng)該已經(jīng)發(fā)現(xiàn)了問題(不,它不是Java)。
我想知道是否有一種方法可以在我的IDE中檢查這個函數(shù),而不需要運行代碼或使用Jest測試它。這可能嗎?TypeScript在這方面做得很好,事實上,它是JavaScript中靜態(tài)檢查的最佳工具之一,也就是說,在你的代碼運行之前“測試”它的正確性。
所以,進入TypeScript世界,把文件的擴展名從filterByTerm.js改為filterByTerm.ts。有了這個改變,你將發(fā)現(xiàn)一堆錯誤在你的代碼:
你能看到函數(shù)參數(shù)下面的紅色標(biāo)記嗎?從現(xiàn)在開始,我將以文本形式向你展示錯誤,但請記住,ide和文本編輯器會在你在TypeScript中出現(xiàn)錯誤時顯示這些紅線。
為了確認(rèn)我們做錯了什么,運行:
npm run tsc
看看這些錯誤:
ilterByTerm.ts:1:23 - error TS7006: Parameter 'input' implicitly has an 'any' type.
filterByTerm.ts:1:30 - error TS7006: Parameter 'searchTerm' implicitly has an 'any' type.
filterByTerm.ts:5:32 - error TS7006: Parameter 'arrayElement' implicitly has an 'any' type.
TypeScript是在告訴你函數(shù)參數(shù)有any類型,如果你記得的話,它可以是TypeScript中的任何類型。我們需要在TypeScript代碼中添加適當(dāng)?shù)念愋妥⑨尅?/p>
等等,到底什么是型?
什么是類型,JavaScript有什么問題?
JavaScript有類型,如果你在知道有字符串、布爾值、數(shù)字、對象等等之前使用過這種語言。到今天為止,JavaScript有8種類型:
String Number BigInt Boolean Null Undefined Object Symbol
列表中的所有內(nèi)容都是“原語”,除了Object是類型。每個JavaScript類型都有一個對應(yīng)的表示,可以在我們的代碼中使用,比如字符串和數(shù)字:
var name = "Hello John";
var age = 33;
JavaScript的“問題”是,變量可以在它(或我們)想要的任何時候改變它的類型。例如,一個布爾值可以在以后變成字符串(將以下代碼保存在名為types.js的文件中):
var aBoolean = false;
console.log(typeof aBoolean); // "boolean"
aBoolean = "Tom";
console.log(typeof aBoolean); // "string"
轉(zhuǎn)換可以是有意的,開發(fā)人員可能真的想將“Tom”分配給aBoolean,但是這類錯誤很有可能是偶然發(fā)生的。
現(xiàn)在,從技術(shù)上講,JavaScript本身并沒有什么問題,因為它的“類型動態(tài)性”是有意為之的。JavaScript是作為一種簡單的web腳本語言而誕生的,而不是作為一種成熟的企業(yè)語言。
然而,JavaScript放松自然會在代碼中造成嚴(yán)重的問題,并破壞其可維護性。TypeScript旨在通過在JavaScript中添加強類型來解決這些問題。事實上,如果你把types.js的擴展改為types。你會在IDE中看到TypeScript在抱怨。
編譯 types.ts 會產(chǎn)生:
types.ts:4:1 - error TS2322: Type '"Tom"' is not assignable to type 'boolean'.
涉足TypeScript類型
TypeScript圍繞著類型展開,而我們的代碼看起來根本沒有類型。是時候加一些了。我們首先要確定函數(shù)參數(shù)。通過查看函數(shù)的調(diào)用方式,可以看出它有兩個字符串作為參數(shù):
filterByTerm("input string", "java");
我們確定嗎?讓我們向函數(shù)添加第一個類型注釋。方法如下:
function filterByTerm(input: string, searchTerm: string) {
// omitted
}
// omitted
就是這樣!通過給參數(shù)添加類型,我們將代碼從純JavaScript遷移到TypeScript。但如果你試圖編譯代碼:
npm run tsc
發(fā)生了什么:
filterByTerm.ts:5:16 - error TS2339: Property 'filter' does not exist on type 'string'.
你能看到TypeScript是如何引導(dǎo)你的嗎?問題在于過濾器函數(shù):
function filterByTerm(input: string, searchTerm: string) {
// omitted
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
我們告訴TypeScript“input”是一個字符串,但在后面的代碼中,我們對它調(diào)用了filter方法,它屬于數(shù)組。我們真正想要的是將"input"標(biāo)記為一個數(shù)組,也許是一個字符串?dāng)?shù)組?
為此,您有兩種選擇。選項1帶有string[]:
function filterByTerm(input: string[], searchTerm: string) {
// omitted
}
或者,如果你喜歡這個語法,選項2 Array<string>:
function filterByTerm(input: Array<string>, searchTerm: string) {
// omitted
}
我個人更喜歡第二種選擇?,F(xiàn)在讓我們嘗試再次編譯(npm運行tsc),下面是:
filterByTerm.ts:10:14 - error TS2345: Argument of type '"input string"' is not assignable to parameter of type 'string[]'.
filterByTerm("input string", "java");
我們將input標(biāo)記為一個字符串?dāng)?shù)組,現(xiàn)在我們試圖傳入一個字符串。這很容易解決!讓我們來傳遞一個字符串?dāng)?shù)組:
filterByTerm(["string1", "string2", "string3"], "java");
下面是到目前為止的完整代碼:
function filterByTerm(input: Array<string>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
filterByTerm(["string1", "string2", "string3"], "java");
我覺得不錯。但如果你編譯它就不是(npm運行tsc):
filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'string'.
好的,TypeScript,很好。我們傳入一個字符串?dāng)?shù)組,但在稍后的代碼中,我們嘗試訪問一個名為“url”的屬性:
return arrayElement.url.match(regex);
初學(xué)者TypeScript教程:TypeScript對象和接口
因為filterByTerm被傳遞給了一個字符串?dāng)?shù)組,所以TypeScript就開始抱怨了。"url"屬性不存在類型字符串TypeScript。讓我們通過傳遞一個對象數(shù)組來幫助TypeScript,其中每個對象都有需要的url屬性:
filterByTerm(
[{ url: "string1" }, { url: "string2" }, { url: "string3" }],
"java"
);
當(dāng)你在那里的時候,更新函數(shù)簽名,讓它接受一個對象數(shù)組:
function filterByTerm(input: Array<object>, searchTerm: string) {
// omitted
}
現(xiàn)在讓我們編譯代碼:
npm run tsc
發(fā)生
filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'object'.
又來了!這是有意義的,至少在TypeScript中是這樣:一般的JavaScript對象沒有任何名為“url”的屬性。對我來說,這是TypeScript真正開始發(fā)光的地方。
關(guān)鍵是,你不能給一個隨機對象分配屬性,然后就完事了。TypeScript要求代碼中的每個實體都符合特定的形狀。這個形狀在TypeScript中有一個名字:interface。
現(xiàn)在,一開始它看起來像陌生的語法,但一旦你習(xí)慣了接口,你就會開始在所有地方使用它們。但是什么是界面呢?TypeScript中的接口就像一個合同。換句話說,接口就像實體的“模型”。
看看我們的代碼,我們可以想到一個簡單的“模型”,命名為Link,對象的形狀應(yīng)該符合以下模式:
它必須有一個類型為string的url屬性
在TypeScript中,你可以用一個接口來定義這個“模型”,就像這樣(把下面的代碼放在filterByTerm.ts的頂部:
interface Link {
url: string;
}
在接口聲明中,我們說:“從現(xiàn)在開始,我想在我的TypeScript代碼中使用這個形狀。”當(dāng)然,這不是有效的JavaScript語法,它將在編譯過程中被刪除。
現(xiàn)在,我們可以通過修改參數(shù)"input"來使用我們的接口,它實際上也是一個自定義的TypeScript類型:
function filterByTerm(input: Array<Link>, searchTerm: string) {
// omitted
}
在這個修復(fù)中,我們對TypeScript說“期待一個Link數(shù)組”作為該函數(shù)的輸入。下面是完整的代碼:
interface Link {
url: string;
}
function filterByTerm(input: Array<Link>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
filterByTerm(
[{ url: "string1" }, { url: "string2" }, { url: "string3" }],
"java"
);
現(xiàn)在所有的錯誤都應(yīng)該消失了,你可以運行:
npm run tsc
編譯步驟將在項目文件夾中生成一個名為filterByTerm.js的文件,其中包含純JavaScript代碼。你可以簽出這個文件,看看TypeScript的特定聲明是如何被去掉的。
因為"always sstrict "被設(shè)置為true, TypeScript編譯器也會在filterByTerm.js的頂部發(fā)出"use strict"。
你的第一個TypeScript代碼做得很好!在下一節(jié)中,我們將進一步探討接口。
TypeScript新手教程:接口和字段
TypeScript接口是該語言最強大的結(jié)構(gòu)之一。接口有助于在應(yīng)用程序中形成“模型”,以便任何開發(fā)人員在編寫代碼時都可以選擇該模型并遵循它。
到目前為止,我們定義了一個簡單的接口Link:
interface Link {
url: string;
}
如果你想在接口中添加更多的字段,你需要在block中聲明它們:
interface Link {
description: string;
id: number;
url: string;
}
現(xiàn)在任何Link類型的對象都必須“實現(xiàn)”新字段,否則就會出現(xiàn)錯誤。實際上,通過編譯代碼:
npm run tsc
TypeScript對你吼叫:
filterByTerm.ts:17:4 - error TS2739: Type '{ url: string; }' is missing the following properties from type 'Link': description, id
問題是函數(shù)的參數(shù):
filterByTerm(
[{ url: "string1" }, { url: "string2" }, { url: "string3" }],
"java"
);
TypeScript可以通過查看函數(shù)聲明來推斷參數(shù)的類型是Array of Link。因此,該數(shù)組中的任何對象必須具有(實現(xiàn))接口鏈接中定義的所有字段。
大多數(shù)情況下,這還遠(yuǎn)遠(yuǎn)不夠理想。畢竟,我們不知道每個Link類型的新對象是否都會有所有的字段。不用擔(dān)心,要讓編譯通過,我們可以用一個問號聲明接口的字段是可選的:
interface Link {
description?: string;
id?: number;
url: string;
}
TypeScript新手教程:變量類型
到目前為止,你已經(jīng)看到了如何向函數(shù)的形參添加類型:
function filterByTerm(input: Array<Link>, searchTerm: string) {
//
}
TypeScript并不局限于此,當(dāng)然,你也可以給任何變量添加類型。為了說明這個概念,讓我們逐個提取函數(shù)的參數(shù)。首先,我要提取每個對象:
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
注意我是如何告訴TypeScript obj1, obj2和obj3的類型是Link的。在“香草”JavaScript你會寫:
const obj1 = { url: "string1" };
const obj2 = { url: "string2" };
const obj3 = { url: "string3" };
接下來,我們可以像這樣定義一個Link數(shù)組:
const arrOfLinks: Array<Link> = [obj1, obj2, obj3];
最后是搜索詞:
const term: string = "java";
最后完整的代碼:
interface Link {
description?: string;
id?: number;
url: string;
}
function filterByTerm(input: Array<Link>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
const arrOfLinks: Array<Link> = [obj1, obj2, obj3];
const term: string = "java";
filterByTerm(arrOfLinks, term);
好的,我理解你。與JavaScript相比,TypeScript看起來更冗長,有時甚至是多余的。但是隨著時間的推移,您會發(fā)現(xiàn)添加的類型越多,您的代碼就越健壯。
通過添加類型注釋,你越多地幫助TypeScript理解代碼的意圖,你以后就會越好。這樣你的開發(fā)經(jīng)驗就會飛速增長。
例如,現(xiàn)在arrOfLinks與正確的類型(Link的數(shù)組)相關(guān)聯(lián),編輯器可以推斷數(shù)組中的每個對象都有一個名為url的屬性,就像Link接口中定義的那樣:
現(xiàn)在告訴我這不是很棒,因為它確實很棒。除了字符串、數(shù)組和數(shù)字,TypeScript還有很多其他類型。
有布爾值,元組,"any", never,枚舉。假以時日,你會全都學(xué)會的。如果您好奇,請查看基本類型的文檔。
現(xiàn)在讓我們繼續(xù)擴展接口。
(大多數(shù)時候,Typescript可以自己推斷類型。作為經(jīng)驗法則,讓它為你發(fā)揮作用吧!)
TypeScript新手教程:擴展接口
TypeScript接口很棒。然而,總有一天你需要在你的代碼中添加一個新的實體,而這個實體恰好與另一個現(xiàn)有的接口幾乎相同。例如,我們想要一個名為TranslatedLink的新接口,具有以下屬性:
id, number url, string description, string language, string
描述、id和url…看起來我們已經(jīng)有了具有相同屬性的Link接口:
interface Link {
description?: string;
id?: number;
url: string;
}
有辦法重用接口鏈接嗎?原來,在TypeScript中,我們可以通過將接口的屬性賦值給新接口來擴展接口,比如TranslatedLink就從Link“繼承”了一些特性。下面是如何做到這一點,注意關(guān)鍵字extends:
interface Link {
description?: string;
id?: number;
url: string;
}
interface TranslatedLink extends Link {
language: string;
}
現(xiàn)在,任何TranslatedLink類型的對象都將具有可選屬性description、id、url和新屬性language:
interface Link {
description?: string;
id?: number;
url: string;
}
interface TranslatedLink extends Link {
language: string;
}
const link1: TranslatedLink = {
description:
"TypeScript tutorial for beginners is a tutorial for all the JavaScript developers ...",
id: 1,
url: "www.valentinog.com/typescript/",
language: "en"
};
當(dāng)link1這樣的對象使用接口時,我們說link1實現(xiàn)了該接口中定義的屬性。另一方面,當(dāng)接口用于描述代碼中的一個或多個對象時,它就具有了實現(xiàn)。
擴展接口意味著借用它的屬性并擴展它們以實現(xiàn)代碼重用。但是等等,還有更多!你很快就會看到TypeScript接口也可以描述函數(shù)。
但首先讓我們看看索引!
TypeScript新手教程:索引插曲
JavaScript對象是鍵/值對的容器。想象一個簡單的物體:
const paolo = {
name: "Paolo",
city: "Siena",
age: 44
};
我們可以使用點語法訪問任意鍵的值:
console.log(paolo.city);
或者使用括號語法(JavaScript數(shù)組也是如此,因為數(shù)組是一種特殊的對象):
console.log(paolo["city"]);
現(xiàn)在,假設(shè)鍵變成了動態(tài)的,這樣我們就可以把它放到一個變量中,并在括號內(nèi)引用它:
const paolo = {
name: "Paolo",
city: "Siena",
age: 44
};
const key = "city";
console.log(paolo[key]);
現(xiàn)在,讓我們添加另一個對象,將兩個對象都放到數(shù)組中,并像在filterByTerm.js中那樣,使用filter方法過濾數(shù)組。但這一次鍵是動態(tài)傳遞的,因此可以通過任何鍵進行過濾:
const paolo = {
name: "Paolo",
city: "Siena",
age: 44
};
const tom = {
name: "Tom",
city: "Munich",
age: 33
};
function filterPerson(arr, term, key) {
return arr.filter(function(person) {
return person[key].match(term);
});
}
filterPerson([paolo, tom], "Siena", "city");
相關(guān)內(nèi)容如下:
return person[key].match(term);
會工作嗎?是的,因為JavaScript并不關(guān)心paolo或tom是否通過動態(tài)鍵“可索引”。那么TypeScript呢?在這種情況下它會給出一個錯誤嗎?
讓我們看看:在下一節(jié)中,我們將使用可變鍵使filterByTerm更加動態(tài)。
接口可以有索引
讓我們回到filterByTerm。特別是filterByTerm函數(shù):
function filterByTerm(input: Array<Link>, searchTerm: string) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement.url.match(regex);
});
}
它看起來不那么靈活,因為對于每個鏈接,我們都將硬編碼的屬性“url”與正則表達(dá)式匹配。我們可能想讓屬性,也就是鍵,是動態(tài)的。以下是第一個嘗試:
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
});
}
lookupKey是動態(tài)鍵,它也會被分配一個默認(rèn)參數(shù)作為回退,即字符串“url”。讓我們編譯代碼:
npm run tsc
報錯
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Link'.
No index signature with a parameter of type 'string' was found on type 'Link'.
這里是違規(guī)的一行:
return arrayElement[lookupKey].match(regex);
“沒有索引簽名”。哇。這是一個“容易”的修復(fù)。Head over the interface Link并添加索引:
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: string;
}
語法有點奇怪,但類似于對象上的動態(tài)鍵訪問。這意味著我們可以通過string類型的索引訪問該對象的任何鍵,而該索引又返回另一個字符串。
不管怎樣,第一次嘗試會出現(xiàn)其他錯誤,比如:
error TS2411: Property 'description' of type 'string | undefined' is not assignable to string index type 'string'.
error TS2411: Property 'id' of type 'number | undefined' is not assignable to string index type 'string'.
這是因為接口上的一些屬性是可選的,可能是未定義的,并且類型并不總是字符串(例如id是一個數(shù)字)。
我們可以嘗試用聯(lián)合類型來解決這個問題,這是一種TypeScript語法,用來定義兩個或更多其他類型之間的聯(lián)合類型:
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: string | number | undefined;
}
以下行:
[index: string]: string | number | undefined;
表示index是一個字符串,可能返回另一個字符串、數(shù)字或未定義的值。嘗試再次編譯,這里有另一個錯誤:
error TS2339: Property 'match' does not exist on type 'string | number'.
return arrayElement[lookupKey].match(regex);
是有意義的。match方法只對字符串有效,并且我們的索引有可能返回一個數(shù)字。為了修復(fù)這個錯誤,我們可以使用anyas作為一個解決方案:
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: any;
}
TypeScript編譯器又高興了,但代價是什么呢?
現(xiàn)在是時候把注意力轉(zhuǎn)向TypeScript的另一個基本特性了:函數(shù)的返回類型。
TypeScript新手教程:函數(shù)的返回類型
到目前為止有很多新東西??傊?,我跳過了TypeScript的另一個有用特性:函數(shù)的返回類型。
要理解為返回值添加類型注釋為什么很方便,請想象一下我正在擺弄您的奇特函數(shù)。這是原始版本:
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
});
}
如果按原樣調(diào)用,傳入之前看到的鏈接數(shù)組和搜索詞“string3”,它應(yīng)該返回一個對象數(shù)組,如預(yù)期的那樣:
filterByTerm(arrOfLinks, "string3");
// EXPECTED OUTPUT:
// [ { url: 'string3' } ]
但現(xiàn)在考慮一下另一種變體:
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
) {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input
.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
})
.toString();
}
如果現(xiàn)在調(diào)用,使用相同的Link數(shù)組和搜索詞“string3”,它將返回"[object Object]"!:
filterByTerm(arrOfLinks, "string3");
// WRONG OUTPUT:
// [object Object]
你能發(fā)現(xiàn)問題嗎?提示:toString。
該函數(shù)沒有按照預(yù)期工作,除非到達(dá)生產(chǎn)環(huán)境(或測試代碼),否則您永遠(yuǎn)不會知道。幸運的是,TypeScript可以捕捉到這些錯誤,就像你在編輯器中寫的那樣。
這里是修復(fù)(只是相關(guān)的部分):
function filterByTerm(/* omitted for brevity */): Array<Link> {
/* omitted for brevity */
}
它是如何工作的?通過在函數(shù)體前添加類型注釋,我們告訴TypeScript可以期待另一個數(shù)組作為返回值。現(xiàn)在這個漏洞很容易被發(fā)現(xiàn)。以下是目前為止的代碼(修改版本):
interface Link {
description?: string;
id?: number;
url: string;
[index: string]: any;
}
function filterByTerm(
input: Array<Link>,
searchTerm: string,
lookupKey: string = "url"
): Array<Link> {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input
.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
})
.toString();
}
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
const arrOfLinks: Array<Link> = [obj1, obj2, obj3];
filterByTerm(arrOfLinks, "string3");
編譯代碼
npm run tsc
并檢查錯誤:
error TS2322: Type 'string' is not assignable to type 'Link[]'.
太棒了。我們期待的是鏈接的數(shù)組,而不是字符串。要修復(fù)錯誤,請從過濾器末尾刪除. tostring(),并再次編譯代碼?,F(xiàn)在應(yīng)該可以了!
我們向代碼添加了另一層保護。當(dāng)然,這個bug可以通過單元測試發(fā)現(xiàn)。TypeScript是一個很好的安全層,而不是測試的完全替代。
讓我們繼續(xù)探索類型別名!
TypeScript新手教程:類型別名vs接口
到目前為止,我們已經(jīng)看到了接口作為描述對象和自定義類型的工具。但在其他人的代碼中,您可能也會注意到關(guān)鍵字類型。
顯然,interface和type在TypeScript中可以互換使用,但它們在很多方面是不同的。這讓TypeScript初學(xué)者感到困惑。
記住:TypeScript中的接口是某種東西的形狀,大多數(shù)時候是一個復(fù)雜對象。
另一方面,類型也可以用來描述自定義形狀,但它只是一個別名,或者換句話說,是自定義類型的標(biāo)簽。例如,讓我們想象一個有幾個字段的接口,其中一個是boolean、number和string的聯(lián)合類型:
interface Example {
authenticated: boolean | number | string;
name: string;
}
例如,通過類型別名,您可以提取自定義的聯(lián)合類型,并創(chuàng)建名為Authenticated的標(biāo)簽:
type Authenticated = boolean | number | string;
interface Example {
authenticated: Authenticated;
name: string;
}
通過這種方式,您可以隔離更改,這樣就不必在整個代碼庫中復(fù)制/粘貼union類型。
如果您想將type應(yīng)用于我們的示例(filterByTerm),請創(chuàng)建一個名為Links的新標(biāo)簽,并將Array分配給它。這樣你就可以引用類型:
// the new label
type Links = Array<Link>;
// the new label
function filterByTerm(
input: Links,
searchTerm: string,
lookupKey: string = "url"
): Links {
if (!searchTerm) throw Error("searchTerm cannot be empty");
if (!input.length) throw Error("input cannot be empty");
const regex = new RegExp(searchTerm, "i");
return input.filter(function(arrayElement) {
return arrayElement[lookupKey].match(regex);
});
}
const obj1: Link = { url: "string1" };
const obj2: Link = { url: "string2" };
const obj3: Link = { url: "string3" };
const arrOfLinks: Links = [obj1, obj2, obj3];
filterByTerm(arrOfLinks, "string3");
現(xiàn)在,這不是標(biāo)簽類型最聰明的例子但你應(yīng)該明白要點。那么在接口和類型之間應(yīng)該使用什么呢?我更喜歡復(fù)雜對象的接口。TypeScript文檔也建議了一種方法:
因為軟件的理想屬性是對擴展開放的,所以如果可能的話,應(yīng)該始終在類型別名上使用接口。
希望這有助于澄清你的疑惑。
在下一節(jié)中,在告別之前,我們將快速瀏覽另外兩個TypeScript主題。繼續(xù)前進!
TypeScript初學(xué)者教程:更多關(guān)于接口和對象的內(nèi)容
函數(shù)是JavaScript的第一類公民,而對象是語言中最重要的實體。
對象大多是鍵/值對的容器,它們也可以容納函數(shù)也就不足為奇了。當(dāng)函數(shù)駐留在對象內(nèi)部時,它可以通過關(guān)鍵字this訪問“host”對象:
const tom = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
console.log(`${this.name} - ${this.city}`);
}
};
到目前為止,你已經(jīng)看到TypeScript接口應(yīng)用于描述字符串和數(shù)字的簡單對象。但他們能做的還有很多。讓我們舉個例子。創(chuàng)建一個名為interfaces-functions的新文件。ts,代碼如下:
const tom = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
console.log(`${this.name} - ${this.city}`);
}
};
它是一個JavaScript對象,但它需要類型。讓我們做一個接口,使tom符合一個定義良好的形狀。“IPerson”怎么樣?同時,讓我們也把新的接口應(yīng)用到tom:
interface IPerson {
name: string;
city: string;
age: number;
}
const tom: IPerson = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
console.log(`${this.name} - ${this.city}`);
}
};
編譯報錯
interfaces-functions.ts:11:3 - error TS2322: Type '{ name: string; city: string; age: number; printDetails: () => void; }' is not assignable to type 'IPerson'.
Object literal may only specify known properties, and 'printDetails' does not exist in type 'IPerson'.
很酷,IPerson沒有任何名為printDetails的屬性,但更重要的是它應(yīng)該是一個函數(shù)。幸運的是,TypeScript接口也可以描述函數(shù)。方法如下:
interface IPerson {
name: string;
city: string;
age: number;
printDetails(): void;
}
這里我們添加了一個類型函數(shù)的屬性printDetails,返回void。void作為函數(shù)的返回值很有用…不要返回任何東西。
輸出到控制臺的函數(shù)實際上不返回任何東西。如果要從printDetails返回一個字符串,可以將返回類型調(diào)整為string:
interface IPerson {
name: string;
city: string;
age: number;
printDetails(): string;
}
const tom: IPerson = {
name: "Tom",
city: "Munich",
age: 33,
printDetails: function() {
return `${this.name} - ${this.city}`;
}
};
現(xiàn)在,如果函數(shù)有參數(shù)呢?在接口中,你可以為它們添加類型注釋:
interface IPerson {
name: string;
city: string;
age: number;
printDetails(): string;
anotherFunc(a: number, b: number): number;
}
如果你開始在實現(xiàn)IPerson的對象中輸入“an…”,大多數(shù)IDE會自動為你完成這個功能。最好的開發(fā)人員生產(chǎn)力。
