TypeScript 接口(Interface)

TypeScript 接口(Interface)

本節介紹 TypeScript 各種類型接口的聲明及其使用方法,接口在 TypeScript 中是極其重要的,我們使用接口來定義契約,如類型命名、屬性檢查、函數類型定義等。

在下一節學習完類之後,你會知道類也可以作爲接口來使用。接口的種類繁多,在學習過程中一定要親手編寫,以達到靈活使用。

1. 慕課解釋

TypeScript 的核心原則之一是對值所具有的結構進行類型檢查。 它有時被稱做“鴨式辨型法”或“結構性子類型化”。 在 TypeScript 裏,接口的作用就是爲這些類型命名和爲你的代碼或第三方代碼定義契約。——官方定義

接口是對 JavaScript 本身的隨意性進行約束,通過定義一個接口,約定了變量、類、函數等應該按照什麼樣的格式進行聲明,實現多人合作的一致性。TypeScript 編譯器依賴接口用於類型檢查,最終編譯爲 JavaScript 後,接口將會被移除。

// 語法格式
interface DemoInterface {

}

2. 應用場景

在聲明一個對象函數或者時,先定義接口,確保其數據結構的一致性。

在多人協作時,定義接口尤爲重要。

3. 接口的好處

過去我們寫 JavaScript 定義一個函數:

function getClothesInfo(clothes) {
  console.log(clothes.price)
}

let myClothes = {
  color: 'black', 
  size: 'XL', 
  price: 98 
}
getClothesInfo(myClothes)

之前我們寫 JavaScript 這樣是很正常的,但同時你可能會遇到下面這些問題:

getClothesInfo() // Uncaught TypeError: Cannot read property 'price' of undefined
getClothesInfo({ color: 'black' }) // undefined

相信原因你也知道,JavaScript 是 弱類型 語言,並不會對傳入的參數進行任何檢測,錯誤在運行時才被發現。那麼通過定義 接口,在編譯階段甚至開發階段就避免掉這類錯誤,接口將檢查類型是否和某種結構做匹配。

3.1 舉例說明

下面通過接口的方式重寫之前的例子:

案例演示

interface Clothes {
  color: string;
  size: string;
  price: number;
}

function getClothesInfo(clothes: Clothes) {
  console.log(clothes.price)
}

let myClothes: Clothes = { 
  color: 'black', 
  size: 'XL', 
  price: 98 
}

getClothesInfo(myClothes)

運行案例點擊 "運行案例" 可查看在線運行效果

代碼解釋: 代碼中,定義了一個接口 Clothes,在傳入的變量 clothes 中,它的類型爲 Clothes。這樣,就約束了這個傳入對象的 外形 與接口定義一致。只要傳入的對象滿足上面的類型約束,那麼它就是被允許的。

Tips:

  1. 定義接口要 首字母大寫

  2. 只需要關注值的 外形,並不像其他語言一樣,定義接口是爲了實現。

  3. 如果沒有特殊聲明,定義的變量比接口少了一些屬性是不允許的,多一些屬性也是不允許的,賦值的時候,變量的形狀必須和接口的形狀保持一致。

4. 接口的屬性

4.1 可選屬性

接口中的屬性不全是必需的。可選屬性的含義是該屬性在被變量定義時可以不存在。

// 語法
interface Clothes {
  color?: string;
  size: string;
  price: number;
}

// 這裏可以不定義屬性 color
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98 
}

帶有可選屬性的接口與普通的接口定義差不多,只是在可選屬性名字定義的後面加一個 ? 符號。

這時,仍不允許添加未定義的屬性,如果引用了不存在的屬性時 TS 將直接捕獲錯誤。

4.2 只讀屬性

一些對象屬性只能在對象剛剛創建的時候修改其值。你可以在屬性名前用 readonly 來指定只讀屬性,比如價格是不能被修改的:

// 語法
interface Clothes {
  color?: string;
  size: string;
  readonly price: number;
}

// 創建的時候給 price 賦值
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98 
}

// 不可修改
myClothes.price = 100
// error TS2540: Cannot assign to 'price' because it is a constant or a read-only property

TypeScript 可以通過 ReadonlyArray<T> 設置數組爲只讀,那麼它的所有寫方法都會失效。

let arr: ReadonlyArray<number> = [1,2,3,4,5];
arr[0] = 6; // Index signature in type 'readonly number[]' only permits reading

代碼解釋: 代碼中的泛型語法在之後會有專門的小節介紹。

4.2.1 readonly vs const

最簡單判斷該用 readonly 還是 const 的方法是看要把它做爲變量使用還是做爲一個屬性。做爲 變量 使用的話用 const,若做爲 屬性 則使用 readonly。

4.3 任意屬性

有時候我們希望接口允許有任意的屬性,語法是用 [] 將屬性包裹起來:

// 語法
interface Clothes {
  color?: string;
  size: string;
  readonly price: number;
  [propName: string]: any;
}

// 任意屬性 activity
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98,
  activity: 'coupon'
}

代碼解釋: 這裏的接口 Clothes 可以有任意數量的屬性,並且只要它們不是 color size 和 price,那麼就無所謂它們的類型是什麼。

  • 項目案例:使用 axios 庫發起 HTTP 傳輸的時候,可以寫入一個自定義的屬性,就是因爲源碼中定義了一個任意屬性:
this.$axios({
  method: 'put',
  url: '/cms/user',
  data: {
    nickname: this.nickname,
  },
  showBackend: true,
})

5. 函數類型

除了描述帶有屬性的普通對象外,接口也可以描述函數類型。

爲了使接口表示函數類型,我們需要給接口定義一個調用簽名。 它就像是一個只有 參數列表 和 返回值類型 的函數定義。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean {
  return source.search(subString) > -1;
}

對於函數類型的類型檢查來說,函數的參數名不需要與接口裏定義的名字相匹配。你可以改變函數的參數名,只要保證函數參數的位置不變。函數的參數會被逐個進行檢查:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
// source => src, subString => sub
mySearch = function(src: string, sub: string): boolean {
  return src.search(sub) > -1;
}

如果你不想指定類型,TypeScript 的類型系統會推斷出參數類型,因爲函數直接賦值給了 SearchFunc 類型變量。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(src, sub) {
  let result = src.search(sub);
  return result > -1;
}

如果接口中的函數類型帶有函數名,下面兩種書寫方式是等價的:

interface Calculate {
  add(x: number, y: number): number
  multiply: (x: number, y: number) => number
}

6. 可索引類型

可索引類型接口讀起來有些拗口,直接看例子:

// 正常的js代碼
let arr = [1, 2, 3, 4, 5]
let obj = {
  brand: 'imooc',
  type: 'education'
}

arr[0]
obj['brand']

再來看定義可索引類型接口:

interface ScenicInterface {
  [index: number]: string
}

let arr: ScenicInterface = ['西湖', '華山', '故宮']
let favorite: string = arr[0]

示例中索引簽名是 number類型,返回值是字符串類型。

另外還有一種索引簽名是 字符串類型。我們可以同時使用兩種類型的索引,但是數字索引的返回值必須是字符串索引返回值類型的子類型。通過下面的例子理解這句話:

// 正確
interface Foo {
  [index: string]: number;
  x: number;
  y: number;
}

// 錯誤
interface Bar {
  [index: string]: number;
  x: number;
  y: string; // Error: y 屬性必須爲 number 類型
}

代碼解釋:

第 12 行,語法錯誤是因爲當使用 number 來索引時,JavaScript 會將它轉換成 string 然後再去索引對象。也就是說用 100(一個number)去索引等同於使用"100"(一個string)去索引,因此兩者需要保持一致。

7. 類類型

我們希望類的實現必須遵循接口定義,那麼可以使用 implements 關鍵字來確保兼容性。

這種類型的接口在傳統面嚮對象語言中最爲常見,比如 java 中接口就是這種類類型的接口。這種接口與抽象類比較相似,但是接口只能含有抽象方法和成員屬性,實現類中必須實現接口中所有的抽象方法和成員屬性。

interface AnimalInterface {
  name: string;
}

class Dog implements AnimalInterface {
  name: string;
  
  constructor(name: string){
    this.name = name
  }
}

你也可以在接口中描述一個方法,在類裏實現它:

interface AnimalInterface {
  name: string

  eat(m: number): string
}

class Dog implements AnimalInterface {
  name: string;

  constructor(name: string){
    this.name = name
  }

  eat(m: number) {
    return `${this.name}吃肉${m}分鐘`
  }
}
 

接口描述了類的公共部分,而不是公共和私有兩部分。 它不會幫你檢查類是否具有某些私有成員。

8. 繼承接口

和類一樣,接口也可以通過關鍵字 extents 相互繼承。 這讓我們能夠從一個接口裏複製成員到另一個接口裏,可以更靈活地將接口分割到可重用的模塊裏。

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square = {} as Square;
// 繼承了 Shape 的屬性
square.color = "blue";
square.sideLength = 10;

一個接口可以繼承多個接口,創建出多個接口的合成接口。

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

9. 混合類型

在前面已經介紹,接口可以描述函數、對象的方法或者對象的屬性。

有時希望一個對象同時具有上面提到多種類型,比如一個對象可以當做函數使用,同時又具有屬性和方法。

interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  let counter = function (start: number) { } as Counter;
  counter.interval = 123;
  counter.reset = function () { };
  return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

代碼解釋:

第 1 行,聲明一個接口,如果只有 (start: number): string 一個成員,那麼這個接口就是函數接口,同時還具有其他兩個成員,可以用來描述對象的屬性和方法,這樣就構成了一個混合接口。

第 7 行,創建一個 getCounter() 函數,它的返回值是 Counter 類型的。

let counter = function (start: number) { } as Counter;

第 8 行,通過類型斷言,將函數對象轉換爲 Counter 類型,轉換後的對象不但實現了函數接口的描述,使之成爲一個函數,還具有 interval 屬性和 reset() 方法。斷言成功的條件是,兩個數據類型只要有一方可以賦值給另一方,這裏函數類型數據不能賦值給接口類型的變量,因爲它不具有 interval 屬性和 reset() 方法。

類型斷言在之後的小節也會單節介紹。

10. 小結

本節介紹了接口的基本用法及其使用場景,接口在 TypeScript 中至關重要,TypeScript 編譯器依賴接口用於類型檢查。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章