TypeScript 中的接口是一個非常靈活的概念。除了抽象類的部分行為外,它還經(jīng)常被用來描述“一個對象的形狀”。

01.必需的屬性
定義接口時,需要使用interface關(guān)鍵字:
interface User {
  name: string;
  sex: string;
}
const user: User = {
  name: "Bytefer",
  sex: "male",
};在上面的代碼中,我們定義了一個用戶界面。然后定義一個用戶變量并將其類型設(shè)置為用戶類型。
但是,如果我們給用戶變量賦值,相關(guān)的屬性就丟失了。然后,TypeScript編譯器會提示相關(guān)錯誤。例如,在下面的代碼中,我們在分配時缺少 sex 屬性:

那么如何解決上面的錯誤呢?解決方案之一是使用 ? 在定義接口時聲明一些屬性是可選的。
02.可選屬性
interface User {
  name: string;
  sex?: string;
}
let user: User = { // OK
  name: "Bytefer",
};
user = { // Ok
  name: "Bytefer",
  sex: "male",
};既然不允許缺少屬性,那么可以添加未聲明的屬性嗎?

從上圖可以看出,使用對象字面量賦值時,包括未聲明的age屬性,也會報錯。解決此問題的最簡單方法是向 User 類型添加一個 age 屬性:
interface User {
  name: string;
  sex?: string;
  age: number;
}這種方案雖然可以解決問題,但是如果我們想添加其他任意屬性,這種方式就不太好了。為滿足上述要求,我們可以使用索引簽名。
03.索引簽名
索引簽名的語法如下:

鍵的類型只能是字符串、數(shù)字、符號或模板字面量類型,而值的類型可以是任何類型。
現(xiàn)在我們了解了索引簽名的語法,讓我們更新用戶類型:
interface User {
  name: string;
  sex?: string;
  [propName: string]: any; // Index Signatures
}更新 User 類型,并添加新的 age 和 email 屬性后,TypeScript 編譯器不會提示錯誤。
let user: User = {
  name: "Bytefer",
  sex: "male",
  age: 30,
  email: "bytefer@gmail.com"
};04.只讀屬性
在web系統(tǒng)中,我們需要區(qū)分不同的用戶,一般情況下,我們會使用一個id屬性來標(biāo)識不同的用戶。該屬性由Web系統(tǒng)自動生成,用戶無法修改。對于上面的場景,我們可以使用readonly修飾符來定義只讀屬性。
除了屬性之外,對象還可能包含方法。在使用接口定義對象類型時,我們還可以同時聲明對象上存在的方法:
interface User {
  id: string;
  name: string;
  say(words: string): void;
}
let user: User = {
  id: "6666",
  name: "Bytefer",
  say(words: string) {
    console.log(words);
  },
};05.Call Signatures
描述函數(shù)的最簡單方法是使用函數(shù)類型表達(dá)式,這些類型在語法上類似于箭頭函數(shù):
const log: (msg: string) => void = (msg: string) => {
  console.log(msg);
};
log("Bytefer");語法 (msg: string) => void 的意思是“一個函數(shù),它有一個名為 msg 的參數(shù),類型為字符串,沒有返回值”。當(dāng)然,我們可以使用類型別名來命名函數(shù)類型:
type LogFn = (msg: string) => void;
const log: LogFn = (msg: string) => {
  console.log(msg);
};
如果我們想描述一些可以用屬性調(diào)用的東西,函數(shù)本身也是一個對象。那么函數(shù)類型表達(dá)式不能滿足這個要求。對于這種場景,我們可以在定義對象類型時使用調(diào)用簽名:

需要注意的是,在聲明調(diào)用簽名時,也支持重載:
interface Logger {
  type: string;
  (msg: string): void;
  (msg: string, timestamp: number): void
  (msg: string, timestamp: number, module: string): void
}06.構(gòu)建簽名
除了直接調(diào)用函數(shù),我們還可以使用new運(yùn)算符來調(diào)用函數(shù),一般稱為構(gòu)造函數(shù)。我們可以通過在調(diào)用簽名前添加 new 關(guān)鍵字來編寫構(gòu)造簽名:
interface PointConstructor {
  new (x: number, y: number): { x: number; y: number };
}
function createPoint(ctor: PointConstructor, 
  x: number = 0, y: number = 0) {
  return new ctor(x, y);
}
class Point {
  constructor(public x: number, public y: number) {}
}
const zero = createPoint(Point);
console.log(zero);07.混合類型
那么在定義接口的時候,是否可以同時使用調(diào)用簽名和構(gòu)造簽名呢?答案是肯定的,我們常用的Date對象,它的類型是DateConstructor,其中調(diào)用簽名和構(gòu)造簽名都用到了:
declare var Date: DateConstructor;
在上面的代碼中,除了調(diào)用簽名和構(gòu)造簽名外,還定義了 Date 構(gòu)造函數(shù)上的屬性和方法。
08.通用接口
通用類型也可以與接口一起使用。下面是一個通用接口。
interface KeyPair<T, U> {
  key: T;
  value: U;
}
let kv1: KeyPair<number, string> = { key: 1, value: "Bytefer" };09.擴(kuò)展接口
接口可以擴(kuò)展一個或多個接口,這使得編寫接口靈活且可重用。
interface Point1D {
  x: number;
}
interface Point2D extends Point1D {
  y: number;
}
interface Point3D extends Point2D {
  z: number;
}
const point1D = { x: 0 };
const point2D = { x: 0, y: 0 };
const point3D = { x: 0, y: 0, z: 0 };除了擴(kuò)展單個接口,TypeScript 還允許我們擴(kuò)展多個接口:
interface CanSay {
   say(words: string) :void 
}
interface CanWalk {
  walk(): void;
}
interface Human extends CanSay, CanWalk {
  name: string;
}10.擴(kuò)展類
當(dāng)聲明一個接口時,我們可以擴(kuò)展一個或多個接口。其實我們也可以擴(kuò)展一個聲明的類:
class Point1D {
  public x!: number;
}
interface Point2D extends Point1D {
  y: number;
}
const point2D: Point2D = { x: 0, y: 0 }對于一個類,在聲明類的時候,可以同時實現(xiàn)多個接口:
interface CanSay {
   say(words: string) :void 
}
interface CanWalk {
  walk(): void;
}
class Person implements CanSay, CanWalk {
  constructor(public name: string) {}
  public say(words: string) :void {
    console.log(`${this.name} says:${words}`);  
  }
  public walk(): void {
    console.log(`${this.name} walk with feet`);
  }
}對于 TypeScript 開發(fā)者來說,接口和類型有很多相似之處,當(dāng)然也有一些不同。