【TS 演化史 -- 破曉】一步一個腳印帶你入門 TS

做者:Valentino Gagliardi
譯者:前端小智
來源:valentinog

上個月本身花了 1300 買了阿里的服務器來學習 node 及對應的框架,在 11 號以前它們有作活動,1300 的配置如今一年只要 86 元,三年只要229元,真心以爲很划算了,能夠點擊下面進階進行參與html

https://www.aliyun.com/1111/2...前端


什麼是 TypeScript

官方網站的定義是:TypeScript 是 JS 類型的超集。它假設我們知道什麼是超集,什麼是類型化。爲了簡單起見,你能夠將 TypeScript 看做是 JavaScript 之上的一個外殼java

TypeScript 是一個外殼,由於編寫 TypeScript 的代碼,在編譯以後,,剩下的只是簡單的 JS 代碼。node

可是 JS 引擎沒法讀取 TypeScript 代碼,所以任何 TypeScript 文件都應該通過預翻譯過程,即編譯。只有在第一個編譯步驟以後,才剩下純 JS 代碼,能夠在瀏覽器中運行。稍後會介紹 TypeScript 編譯是如何完成的。git

如今讓咱們記住 TypeScript 是一種特殊的 JS,在瀏覽器中運行以前它須要一個翻譯。github

爲何要使用 TypeScript

剛開始,我們不徹底理解 TypeScript 爲什麼有意義。 你可能會問「 TypeScript 的目的是什麼」。 這是一個很好的問題。web

實際上,一旦它在您的代碼中發現嚴重和愚蠢的錯誤,你就會看到 TypeScript 的好處。更重要的是,TypeScript 會讓代碼變得結構良好並且仍是自動,這些還只是 TypeScript 的一小部分。正則表達式

無論怎樣,也常常有人說 TypeScript 沒用,太過笨拙。typescript

凡事都有兩面性,TypeScript 有不少反對者和支持者,但重要的是 TypeScript 是一個可靠的工具,將它放在我們的工具技能包中不會形成傷害。express

TypeScript 配置

爲何配置? TypeScript 還有一個二進制文件,可將 TypeScript 代碼編譯爲 JS 代碼. 請記住,瀏覽器不理解 TypeScript:

mkdir typescript-tutorial && cd $_
npm init -y

而後安裝 TypeScript

npm i typescript --save-dev

接下來在 package.json 中的 scripts 下添加以下內容,以便我們能夠輕鬆地運行 TypeScript 編譯器:

"scripts": {
    "tsc": "tsc"
  }

tsc 表明 TypeScript 編譯器,只要編譯器運行,它將在項目文件夾中查找名爲tsconfig.json 的文件。 使用如下命令爲 TypeScript 生成配置文件:

npm run tsc -- --init

執行成功後會在控制檯收到 message TS6071: Successfully created a tsconfig.json file。在項目文件夾中會看到新增了一個 tsconfig.json 文件。tsconfig。json 是一個可怕的配置文件,不要慌。我們不須要知道它的每個要點,在下一節中,會介紹入門的相關部分。

配置TypeScript 編譯器

最好先初始化 git repo 並提交原始的 tsconfig.json,而後再打開文件。 咱們將只保留一些配置選項,並刪除其餘全部內容。 稍後,你可能須要將如今的版本與原始版本進行比較。

首先,請打開 tsconfig.json 並將全部原始內容替換爲如下內容:

{
  "compilerOptions": {
    "target": "es5",
    "strict": true
  }
}

保存並關閉文件。 首先,你可能想知道 tsconfig.json 是幹什麼的。 該配置文件由 TypeScript 編譯器和任何具備 TypeScript 支持的代碼編輯器讀取。

  • noImplicitAny true:當變量沒有定義類型時,TypeScript 會報錯
  • alwaysStrict true:嚴格模式是 JS 的安全機制,它能夠防止意外的全局變量,默認的 this 綁定等。 設置爲 「alwaysStrict」 時,TypeScript 在每一個KS 文件的頂部都使用 「use strict」

有更多的配置選項可用。隨着時間的推移,你會學到更多,由於如今上面的兩個選擇是你開始學習時須要知道的一切。

關於類型的幾個詞

TypeScript 支持與 JS 幾乎相同的數據類型,此外,TypeScript 本身添加了更多的類型,如 any 類型同樣。

「any」 是鬆散的 TypeScript 類型。 這意味着:此變量能夠是任何類型:字符串,布爾值,對象等。 實際上,這就像根本沒有類型檢查。

TypeScript 中的行爲

我們從一個合法的 KS函數開始:filterByTerm。在項目文件夾中建立一個名爲 filterByTerm.js 的新文件,並輸入如下內容

function filterByTerm(input, searchTerm) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

filterByTerm("input string", "java");

若是如今不瞭解邏輯,請不要擔憂。 看一下該函數的參數,以及幾行以後如何使用它們。 只需查看代碼,就應該已經發現了問題。

我想知道是否有一種方法能夠在個人 IDE 中檢查這個函數,而不須要運行代碼或者用Jest測試它。這可能嗎? TypeScript 在這方面作得很好,實際上它是 JS 中靜態檢查的最佳工具,也就是說,在代碼運行以前測試代碼的正確性

所以,我們改用 TypeScript ,將文件的擴展名從 filterByTerm.js 改成 filterByTerm.ts。經過這種更改,你會發現代碼中的一堆錯誤

clipboard.png

能夠看到函數參數下的有不少紅色標記。從如今開始,會向你展現文本形式的錯誤,可是請記住,當我們在TypeScript 中出錯時,IDE 和文本編輯器都會顯示這些紅線。

肯定哪一個地方錯:

npm run tsc

能夠看到控制的報錯:

filterByTerm.ts:1:23 - error TS7006: Parameter 'input' implicitly has an 'any' type.

1 function filterByTerm(input, searchTerm) {
                        ~~~~~

filterByTerm.ts:1:30 - error TS7006: Parameter 'searchTerm' implicitly has an 'any' type.

1 function filterByTerm(input, searchTerm) {
                               ~~~~~~~~~~

filterByTerm.ts:5:32 - error TS7006: Parameter 'arrayElement' implicitly has an 'any' type.

5   return input.filter(function(arrayElement) {

TypeScript 告訴你函數參數具備 「any」 類型,若是還記得的話,它能夠是 TypeScript 中的 any 類型。 咱們須要在咱們的 TypeScript 代碼中添加適當的類型註釋。

什麼是類型,JS 中有什麼問題

到目前爲止,JS 有七種類型

  • String
  • Number
  • Boolean
  • Null
  • Undefined
  • Object
  • Symbol (ES6)

除了 Object 類型外,其它是 JS 的基本數據類型。每種 JS 類型都有相應的表示,能夠代碼中使用,好比字符串和數字

var name = "Hello John";
var age = 33;

JS 的問題是,變量能夠隨時更改其類型。例如,布爾值能夠變成字符串(將如下代碼保存到名爲 types.js 的文件中)

var aBoolean = false;
console.log(typeof aBoolean); // "boolean"

aBoolean = "Tom";
console.log(typeof aBoolean); // "string"

轉換能夠是有意的,開發人員可能真的但願將 Tom 分配到 aBoolean,可是這類錯誤極可能是偶然發生的。

從技術上講,JS 自己沒有什麼問題,由於它的類型動態是故意的。JS 是做爲一種簡單的 web 腳本語言而誕生的,而不是做爲一種成熟的企業語言。

然而,JS 鬆散的特性可能會在你的代碼中形成嚴重的問題,破壞其可維護性。TypeScript 旨在經過向 JS 添增強類型來解決這些問題。實際上,若是將 types.js 的擴展更改成 types.ts 。你會在IDE中看到 TypeScript 的抱怨。

types.ts 的編譯控制檯會報錯:

types.ts:4:1 - error TS2322: Type '"Tom"' is not assignable to type 'boolean'.

有了這些知識,接着,我們更深刻地研究 TypeScript 類型。

深刻 TypeScript 類型

TypeScript 強調有類型,我們上面的代碼根本沒有類型,是時候添加一些了。首先要修正函數參數。經過觀察這個函數是如何調用的,它彷佛以兩個字符串做爲參數:

filterByTerm("input string", "java");

爲參數添加類型:

function filterByTerm(input: string, searchTerm: string) {
    // ...
}

// ...

接着編譯:

npm run tsc

剩下的錯誤:

filterByTerm.ts:5:16 - error TS2339: Property 'filter' does not exist on type 'string'.

能夠看到 TypeScript 是如何指導咱們,如今的問題在於 filter 方法。

function filterByTerm(input: string, searchTerm: string) {
  // 省略一些
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

我們告訴 TypeScript 「input」 是一個字符串,可是在後面的代碼中調用了 filter 方法,它屬於數組。咱們真正須要的是將輸入標記爲某個東西的數組,多是字符串數組:

爲此,有兩個選擇。選項1:string[]

function filterByTerm(input: string[], searchTerm: string) {
    // ...
}

選項2: Array<Type>

function filterByTerm(input: Array<string>, searchTerm: string) {
    // ...

}

我我的更喜歡選項2。 如今,嘗試再次編譯(npm run tsc),控制檯信息以下:

filterByTerm.ts:10:14 - error TS2345: Argument of type '"input string"' is not assignable to parameter of type 'string[]'.

filterByTerm("input string", "java");

TypeScript 還會校驗傳入的類型。 咱們將 input 改成字符串數組:

filterByTerm(["string1", "string2", "string3"], "java");

這是到目前爲止的完整代碼:

function filterByTerm(input: Array<string>, searchTerm: string) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

filterByTerm(["string1", "string2", "string3"], "java");

看上去很好,可是,編譯(npm run tsc)仍是過不了:

filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'string'.

TypeScript 確實很嚴謹。 咱們傳入了一個字符串數組,可是在代碼後面,嘗試訪問一個名爲 「url」 的屬性:

return arrayElement.url.match(regex);

這意味着我們須要一個對象數組,而不是字符串數組。我們在下一節中解決這個問題。

TypeScript 對象和接口

上面遺留一個問題:由於 filterByTerm 被傳遞了一個字符串數組。url 屬性在類型爲 string 的TypeScript 上不存在。因此我們改用傳遞一個對象數組來解決這個問題:

filterByTerm(
  [{ url: "string1" }, { url: "string2" }, { url: "string3" }],
  "java"
);

函數定義也要對應的更改:

function filterByTerm(input: Array<object>, searchTerm: string) {
    // omitted
}

如今讓咱們編譯代碼

npm run tsc

控制輸出:

filterByTerm.ts:6:25 - error TS2339: Property 'url' does not exist on type 'object'.

又來了,通用 JS 對象沒有任何名爲 url 的屬性。對我來講,TypeScript 對類型要求真的是很嚴謹。

這裏的問題是,我們不能給一個隨機對象分配屬性,TypeScript 的核心原則之一是對值所具備的結構進行類型檢查, 在 TypeScript 裏,接口(interface)的做用就是爲這些類型命名和爲你的代碼或第三方代碼定義契約,我們可使用接口來解決這個問題。

經過查看咱們的代碼,咱們能夠想到一個名爲 Link 的簡單"模型",其結構應該符合如下模式:它必須有一個類型爲 stringurl 屬性。

在TypeScript 中,你能夠用一個接口來定義這個模型,就像這樣(把下面的代碼放在 filterByTerm.ts 的頂部):

interface ILink {
  url: string;
}

對於接口聲明,這固然不是有效的 JS 語法,在編譯過程當中會被刪除。

提示:在定義接口名字前面加上大寫的I,這是 TypeScript 的慣例。

如今,使用使用接口 ILink 定義 input 類型

function filterByTerm(input: Array<ILink>, searchTerm: string) {
    // ...
}

經過此修復,能夠說 TypeScript 「指望 ILink 數組」做爲該函數的輸入,如下是完整的代碼:

interface ILink {
  url: string;
}

function filterByTerm(input: Array<ILink>, searchTerm: string) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

filterByTerm(
  [{ url: "string1" }, { url: "string2" }, { url: "string3" }],
  "java"
);

此時,全部的錯誤都應該消失了,能夠運行了:

npm run tsc

編譯後,會在項目文件夾中生成一個名爲 filterByTerm.js 的文件,其中包含純 JS 代碼。能夠檢出該文件並查看 TypeScript 特定的聲明最終轉換成 JS 的是什麼樣的。

由於 alwaysStrict 設置爲 true,因此 TypeScript 編譯器也會在 filterByTerm.js 的頂部使用 use strict

接口和字段

TypeScript 接口是該語言最強大的結構之一。接口有助於在整個應用程序中造成模型,這樣任何開發人員在編寫代碼時均可以選擇這種模型並遵循它。

前面,我們定義了一個簡單的接口 ILink

interface ILink {
  url: string;
}

若是您想要向接口添加更多的字段,只需在塊中聲明它們便可:

interface ILink {
  description: string;
  id: number;
  url: string;
}

如今,類型爲ILink的對象都必須實現新字段,不然就會出現錯誤,若是把上面 的定義從新寫入 filterByTerm.ts 而後從新編譯就會報錯了:

filterByTerm.ts:17:4 - error TS2739: Type '{ url: string; }' is missing the following properties from type 'ILink': description, id

問題在於咱們函數的參數:

filterByTerm(
  [{ url: "string1" }, { url: "string2" }, { url: "string3" }],
  "java"
);

TypeScript 能夠經過函數聲明來推斷參數是 ILink 的類型數組。所以,該數組中的任何對象都必須實現接口 ILink 中定義的全部字段

大多數狀況下,實現全部字段是不太現實的。畢竟,咱也不知道 ILink 類型的每一個新對象是否會須要擁有全部字段。不過不要擔憂,要使編譯經過,能夠聲明接口的字段可選,使用 ? 表示:

interface ILink {
  description?: string;
  id?: number;
  url: string;
}

如今編輯器和編譯器都沒問題了。然而 TypeScript 接口能夠作的更多,在下一節咱們將看到如何擴展它們。但首先簡要介紹一下 TypeScript 中的變量。

變量聲明

到目前爲止,我們已經瞭解瞭如何向函數參數中添加類型:

function filterByTerm(input: Array<ILink>, searchTerm: string) {
    //
}

TypeScript 並不限於此,固然也能夠向任何變量添加類型。爲了說明這個例子,我們一一地提取函數的參數。首先咱要提取每個單獨的對象:

const obj1: ILink = { url: "string1" };
const obj2: ILink = { url: "string2" };
const obj3: ILink = { url: "string3" };

接下來咱們能夠像這樣定義一個 ILink 數組:

const arrOfLinks: Array<ILink> = [obj1, obj2, obj3];

參數 searchTerm 對應的類型能夠這樣:

const term: string = "java";

如下是完整的代碼:

interface ILink {
  description?: string;
  id?: number;
  url: string;
}

function filterByTerm(input: Array<ILink>, searchTerm: string) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

const obj1: ILink = { url: "string1" };
const obj2: ILink = { url: "string2" };
const obj3: ILink = { url: "string3" };

const arrOfLinks: Array<ILink> = [obj1, obj2, obj3];

const term: string = "java";

filterByTerm(arrOfLinks, term);

與 JS 相比,TypeScript看起來更冗長,有時甚至是多餘的。可是隨着時間的推移,會發現添加的類型越多,代碼就越健壯。

經過添加類型註釋,對 TypeScript 的瞭解也越多,還能夠幫助你更好地理解代碼的意圖。

例如,arrOfLinks 與正確的類型(ILink的數組)相關聯,我們編輯器就能夠推斷出數組中的每一個對象都有一個名爲url的屬性,如接口 ILink 中所定義:

clipboard.png

除了字符串、數組和數字以外,TypeScript 還有更多類型。有 booleantuple (元組)any, neverenum 。若是你感興趣,能夠查看文檔

如今,我們繼續擴展接口。

擴展接口

TypeScript 接口很好。可是,若是哪天我們須要一個新的對象,所需的類型跟如今有接口基本差很少。假設咱們須要一個名爲 IPost 的新接口,它具備如下屬性:

  • id, number
  • title, string
  • body, string
  • url, string
  • description, string

該接口的字段其中有些,咱們 ILink 接口都有了。

interface ILink {
  description?: string;
  id?: number;
  url: string;
}

是否有辦法重用接口 ILink ? 在 TypeScript 中,可使用繼承來擴展接口,關鍵字用 extends 表示:

interface ILink {
  description?: string;
  id?: number;
  url: string;
}

interface IPost extends ILink {
  title: string;
  body: string;
}

如今,IPost 類型的對象都將具備可選的屬性 descriptionidurl和必填的屬性 titlebody

interface ILink {
  description?: string;
  id?: number;
  url: string;
}

interface IPost extends ILink {
  title: string;
  body: string;
}

const post1: IPost = {
  description:
    "TypeScript tutorial for beginners is a tutorial for all the JavaScript developers ...",
  id: 1,
  url: "www.valentinog.com/typescript/",
  title: "TypeScript tutorial for beginners",
  body: "Some stuff here!"
};

當像 post1 這樣的對象使用一個接口時,咱們說 post1 實現了該接口中定義的屬性。

擴展接口意味着借用其屬性並擴展它們以實現代碼重用。固然 TypeScript 接口還也能夠描述函數,稍後會看到。

索引

JS 對象是鍵/值對的容器。 以下有一個簡單的對象:

const paolo = {
  name: "Paolo",
  city: "Siena",
  age: 44
};

咱們可使用點語法訪問任何鍵的值:

console.log(paolo.city);

如今假設鍵是動態的,咱們能夠把它放在一個變量中,而後在括號中引用它

const paolo = {
  name: "Paolo",
  city: "Siena",
  age: 44
};

const key = "city";

console.log(paolo[key]);

如今我們添加另外一個對象,將它們都放到一個數組中,並使用filter方法對數組進行篩選,就像咱們在 filterByTerm.js 中所作的那樣。但這一次是動態傳遞的,所以能夠過濾任何對象

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");

這是比較重要的一行行:

return person[key].match(term);

能行嗎 是的,由於 JS 不在意 paolotom 是否可經過動態 [key] 進行「索引化」。 那在 TS 又是怎麼樣的呢?

在下一部分中,咱們將使用動態鍵使 filterByTerm 更加靈活。

接口能夠有索引

讓咱們回到 filterByTerm.tsfilterByTerm 函數

function filterByTerm(input: Array<ILink>, searchTerm: string) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

它看起來不那麼靈活,由於對於每一個 ILink,我們都使用硬編碼方式將屬性 url 與正則表達式相匹配。咱們但願使動態屬性(也就是鍵)讓代碼更靈活:

function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement[lookupKey].match(regex);
  });
}

lookupKey 是動態鍵,這是給它分配了默認參數 「url」。 接着編譯代碼:

npm run tsc

固然會報錯:

error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ILink'.
  No index signature with a parameter of type 'string' was found on type 'ILink'.

出錯行:

return arrayElement[lookupKey].match(regex);

元素隱式具備 "any" 類型,由於類型 「ILink」 沒有索引簽名,須要你添加一個索引到對象的接口,這很容易解決。

轉到接口 ILink 並添加索引:

interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string] : string
}

語法有點奇怪,但相似於對象上的動態鍵訪問。這意味着咱們能夠經過類型爲 string 的索引訪問該對象的任何鍵,該索引反過來又返回另外一個字符串。

不過,這樣寫會引起其它錯誤:

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'.

這是由於接口上的一些屬性是可選的,多是 undefined,並且返回類型不老是string(例如,id 是一個 number)。

interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: string | number | undefined;
}

這一行:

[index: string]: string | number | undefined;

表示該索引是一個字符串,能夠返回另外一個字符串、數字或 undefined。嘗試再次編譯,這裏有另外一個錯誤

error TS2339: Property 'match' does not exist on type 'string | number'.
return arrayElement[lookupKey].match(regex);

報的沒毛病。match 方法只存在字符串中 ,並且咱們的索引有可能返回一個 number。爲了修正這個錯誤,咱們可使用 any 類型:

interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: any;
}

再次編譯經過。

函數的返回類型

到目前爲止有不少新東西。如今來看看 TypeScript 的另外一個有用的特性:函數的返回類型

回到 filterByTerm 函數:

function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
) {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement[lookupKey].match(regex);
  });
}

若是按原樣調用,傳遞前面看到的 ILink 數組和搜索詞string3,則如預期的那樣返回一個對象數組:

filterByTerm(arrOfLinks, "string3"); 

// EXPECTED OUTPUT:
// [ { url: 'string3' } ]

但如今考慮一個更改的變體:

function filterByTerm(
  input: Array<ILink>,
  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();
}

若是如今調用,使用相同的 ILink 數組和搜索詞 string3,它將返回 [object object]

filterByTerm(arrOfLinks, "string3");

// WRONG OUTPUT:
// [object Object]

該函數沒有按照預期工做,若是對 JS 隱式類型轉換不清楚就很難發現問題。幸運的是,TypeScript 能夠捕獲這些錯誤,就像你在編輯器中寫的那樣。

修正以下:

function filterByTerm(/* 省略 */): Array<ILink> {
 /* 省略 */
}

它是如何工做的? 經過在函數體以前添加類型註釋,告訴 TypeScript 指望另外一個數組做爲返回值。如今這個bug 很容易被發現。

interface ILink {
  description?: string;
  id?: number;
  url: string;
  [index: string]: any;
}

function filterByTerm(
  input: Array<ILink>,
  searchTerm: string,
  lookupKey: string = "url"
): Array<ILink> {
  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: ILink = { url: "string1" };
const obj2: ILink = { url: "string2" };
const obj3: ILink = { url: "string3" };

const arrOfLinks: Array<ILink> = [obj1, obj2, obj3];

filterByTerm(arrOfLinks, "string3");

如今編譯並檢查錯誤:

error TS2322: Type 'string' is not assignable to type 'ILink[]'.

我們但願返回值的是 ILink 數組,而不是字符串。要修復此錯誤,從末尾刪除 .tostring() 並從新編譯代碼就好了。

類型別名 vs 接口

到目前爲止,咱們已經將接口視爲描述對象和自定義類型的工具。可是經過其餘人的代碼,你可能也注意到了關鍵字的 type

顯然,interface 和 type 在 TypeScript 中能夠互換使用,可是它們在許多方面有所不一樣,這就是TypeScript 給初學者的困惑。

請記住: TypeScript 中的接口描述是某個東西的結構,大多數狀況下是一個複雜的對象。

另外一方面,type 也能夠用來描述自定義的結構,但它只是一個別名,或者換句話說,是自定義類型的標籤。例如,設想一個有兩個字段的接口,其中一個是布爾型、數字型和字符串型的聯合類型

interface IExample {
  authenticated: boolean | number | string;
  name: string;
}

例如,使用 type 別名 能夠提取自定義聯合類型,並建立名爲 Authenticated 的標籤

type Authenticated = boolean | number | string;

interface IExample {
  authenticated: Authenticated;
  name: string;
}

經過這種方式,咱能夠隔離所作的更改,就沒必要在整個代碼庫中複製/粘貼 聯合類型

若是要將 type 應用上面示例(filterByTerm),建立一個名爲 ILinks 的新標籤,並將 Array <ILink> 分配給它。 這樣,就能夠引用前者:

// the new label
type ILinks = Array<ILink>;
// the new label

function filterByTerm(
  input: ILinks,
  searchTerm: string,
  lookupKey: string = "url"
): ILinks {
  if (!searchTerm) throw Error("searchTerm 不能爲空");
  if (!input.length) throw Error("input 不能爲空");
  const regex = new RegExp(searchTerm, "i");
  return input.filter(function(arrayElement) {
    return arrayElement[lookupKey].match(regex);
  });
}

const obj1: ILink = { url: "string1" };
const obj2: ILink = { url: "string2" };
const obj3: ILink = { url: "string3" };

const arrOfLinks: ILinks = [obj1, obj2, obj3];

filterByTerm(arrOfLinks, "string3");

固然,這不是 type 用法最好事例。那麼在 interfacetype 之間使用哪一個呢? 我更喜歡複雜對象的接口。TypeScript 文檔 也建議了。

一個軟件的理想狀態是能夠擴展,所以,若是可能,應始終在類型別名上使用接口。

更多關於接口和對象的知識點

函數是 JS 中的一等公民,而對象是該語言中最重要的實體。

對象大可能是鍵/值對的容器,它們也能夠保存函數,這一點也不奇怪。當一個函數位於一個對象內部時,它能夠經過關鍵字 this 訪問「宿主」對象:

const tom = {
  name: "前端小智",
  city: "廈門",
  age: 26,
  printDetails: function() {
    console.log(`${this.name} - ${this.city}`);
  }
};

到目前爲止,我們已經看到 TypeScript 接口應用於簡單對象,用於描述字符串和數字。 可是他們能夠作的更多。 舉個例, 使用如下代碼建立一個名爲 interfaces-functions.ts 的新文件:

const tom = {
  name: "前端小智",
  city: "廈門",
  age: 26,
  printDetails: function() {
    console.log(`${this.name} - ${this.city}`);
  }
};

這是一個 JS 對象,我們使用接口 IPerson 給它加上類型:

interface IPerson {
  name: string;
  city: string;
  age: number;
}

const tom: IPerson = {
  name: "前端小智",
  city: "廈門",
  age: 26,
  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的屬性,但更重要的是它應該是一個函數。幸運的是,TypeScript 接口也能夠描述函數。以下所示:

interface IPerson {
  name: string;
  city: string;
  age: number;
  printDetails(): void;
}

在這裏,咱們添加了類型函數的屬性 printDetails,返回 voidvoid 表示不返回任何值。

實際上,打印到控制檯的函數不會返回任何內容。 若是要從 printDetails 返回字符串,則能夠將返回類型調整爲 string

interface IPerson {
  name: string;
  city: string;
  age: number;
  printDetails(): string;
}

const tom: IPerson = {
  name: "前端小智",
  city: "廈門",
  age: 26,
  printDetails: function() {
    return `${this.name} - ${this.city}`;
  }
};

若是函數有參數呢? 在接口中,能夠爲它們添加類型註釋

interface IPerson {
  name: string;
  city: string;
  age: number;
  printDetails(): string;
  anotherFunc(a: number, b: number): number;
}

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:https://www.valentinog.com/bl...

總結

這裏沒法涵蓋每個 TypeScript 特性。例如,省略[了ES2015類及其與接口或更高級類型]6的關係。固然後續會持續介紹。

在這個 TypeScript 教程中,講了:

  • 變量,函數參數和返回值的類型註釋
  • 接口
  • 自定義類型
  • 類型別名

    TS 幫助我們減小一些 JS 代碼隱藏的錯誤。須要重複的是,TypeScript 不能替代測試。 蛤它確實是一個有價值的工具,一開始很難掌握,但徹底值得投資。

交流

阿里雲最近在作活動,低至2折,有興趣能夠看看:https://promotion.aliyun.com/...

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq449245884/xiaozhi

由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。

clipboard.png

每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵

相關文章
相關標籤/搜索