TS學習筆記(二):介面

半掩時光發表於2019-04-20

在面嚮物件語言中,介面(Interfaces)是一個很重要的概念,它是對行為的抽象,而具體如何行動需要由類(classes)去實現(implements),TypeScript 中的介面除了可用於對類的一部分行為進行抽象以外,也常用於對物件的形狀(Shape)進行描述。

介面初探

型別檢查器不會去檢查屬性的順序,只要相應的屬性存在並且型別也是對的就可以。

interface IPerson{
  name: string;
  age: number
}
function say(person: IPerson): void {
  console.log(`my name is ${person.name}, and age is ${person.age}`)
}
let me = {
  name: 'funlee',
  age: 18
}
say(me); // my name is funlee, and age is 18

複製程式碼

可選屬性

定義可選屬性的介面,只需在可選屬性名字的定義後面加一個 ? 符號

interface IPerson {
  name: string;
  age: number;
  love?: string
}
function say(person: IPerson): void {
  if(person.hasOwnProperty('love')) {
    console.log(`my name is ${person.name}, my age is ${person.age}, and I love ${person.love}`);
  } else {
    console.log(`my name is ${person.name}, and age is ${person.age}`);
  }
}
let me = {
  name: 'funlee',
  age: 18,
  love: 'TS'
}
let you = {
  name: 'Jack',
  age: 18
}
say(me); // my name is funlee, my age is 18, and I love TS
say(you) // my name is Jack, and age is 18
複製程式碼

只讀屬性

定義只讀屬性的介面,只需在只讀屬性名前加 readonly

interface IPerson {
  readonly name: string;
  age: number;
  love?: string
}
let me: IPerson = {
  name: 'funlee',
  age: 18
}
me.name = 'new name'; // error!
複製程式碼

額外的屬性檢查

當一個物件字面量裡宣告瞭介面中不存在的屬性時,會報不存在錯誤,即使該屬性已設定為可選屬性,因為該物件字面量會被特殊對待而且會經過額外屬性檢查,繞開額外屬性檢查的方法如下:

  • 使用型別斷言
  • 新增一個字串索引簽名,前提是你能夠確定這個物件可能具有某些做為特殊用途使用的額外屬性
  • 將該物件賦值給另一個變數
// 錯誤寫法,會進行額外檢查
interface IPerson {
  name: string;
  age?: number;
}
let me: IPerson = {
  name: 'funlee',
  love: 'TS'
}

// 方法一:型別斷言
interface IPerson {
  name: string;
  age?: number;
}
let me = {
  name: 'funlee',
  love: 'TS'
} as IPerson

// 方法二:字串索引簽名
interface IPerson {
  name: string;
  age?: number;
  [propName: string]: any;
}
let me: IPerson = {
  name: 'funlee',
  love: 'TS'
}

// 方法三:賦值給另一個變數
interface IPerson {
  name: string;
  age?: number;
}
let me = {
  name: 'funlee',
  love: 'TS'
}
let you: IPerson = me;

複製程式碼

函式型別

介面可以描述函式型別,它定義了函式的引數列表和返回值型別,引數列表裡的每個引數都需要名字和型別,函式的引數名不需要與介面裡定義的名字相匹配,只需要型別相容就可以了。

let getArea: (width: number, height: number) => number = (w: number, h: number): number =>{
  return w * h;
}
console.log(getArea(5, 6)) // 30

複製程式碼

可索引的型別

介面可以描述那些能夠通過索引得到的型別,可索引型別具有一個索引簽名,它描述了物件索引的型別,還有相應的索引值型別,索引簽名支援兩種型別:number 和 string,但是由於 number 實際上會被轉化為 string 型別(根據物件 key 的性質),所以需要遵守:number 索引的返回值型別是 string 索引的返回值型別的子型別。

interface IPerson {
  [index: string]: string;
}
let me: IPerson = {love: 'TS'}
me.name = 'funlee';
me.age = 18; // error

複製程式碼

如果 interface 裡還宣告瞭一個和索引簽名索引返回值型別不匹配的屬性,會報錯

interface ITest {
  [index: string]: string;
  name: string;
  age: 18; // 報錯,因為返回值型別是number,不符合string型別
}

複製程式碼

還可以宣告一個 readonly 的索引簽名

interface IPerson {
  readonly [index: string]: string;
}
let p: IPerson = {name: 'funlee'};
p.love = 'TS'; // error

複製程式碼

類型別

typescript 裡也允許像 Java、C# 那樣,讓一個 class 去實現一個 interface;但是需要注意的是,介面描述的是類的公共部分,而不是公共和私有兩部分,所以不會檢查類是否具有某些私有成員。

interface ISome {
  prop: string // 描述一個屬性
  method(paramA: string, paramB: number) // 描述一個方法
}
class A implements ISome {
  prop: 'propValue'
  method(a: string, b: number) {
    // ...
  }
  constructor(paramA: number){
    // ...
  }
}
複製程式碼

靜態部分與例項部分

首先看一個示例:用構造器簽名定義一個介面,並試圖實現這個介面:

interface Person {
  new(name: string)
}
class People implements Person {
  constructor(name: string) {
    // ...
  }
}
// 報錯:no match for the signature 'new (name: string): any'.
複製程式碼

這是因為:當類實現一個介面時,只對例項部分進行型別檢查,而constructor存在於靜態部分,所以不在檢查的範圍內。 所以做法如下:

// 針對類建構函式的介面
interface CPerson {
  new(name: string)
}
// 針對類的介面
interface IPerson {
  name: string
  age: number
}
function create(c: CPerson, name: string): IPerson {
  return new c(name)
}
class People implements IPerson {
  name: string
  age: number
}
let p = create(People, 'funlee') // 可以
複製程式碼

繼承介面

和類一樣,介面也可以相互繼承,如:

interface Shape {
  color: string;
}
interface Square extends Shape {
  sideLength: number;
}
const square = <Square>{};
square.color = 'blue';
square.sideLength = 10;
複製程式碼

同時,一個介面也可以繼承多個介面,建立出多個介面的合成介面,如:

interface Shape {
  color: string;
}
interface PenStroke {
  penWidth: number;
}
interface Square extends Shape, PenStroke {
  sideLength
}
const square = <Square>{};
square.color = 'blue';
square.sideLength = 10;
square.penWidth = 5.0;
複製程式碼

混合型別

允許讓一個物件同時作為函式和物件使用,並帶有額外的屬性,如:

interface MixedDemo {
  (str: string): void;
  defaultStr: string;
}

function foo(): MixedDemo {
  let x = <MixedDemo>function(str: string){
    console.log(str)
  }
  x.defaultStr = 'Hello, world'
  return x
}

let c = foo();
c('This is a function') // 'This is a function'
console.log(c.defaultStr) // 'Hello, world'
複製程式碼

介面繼承類

介面可以繼承自一個類,從而像宣告瞭所有類中存在的成員,並且private和protected成員也會被繼承,這意味著:只有類自己或子類能夠實現該介面,例子如:

class A {
  protected propA: string
}
interface I extends A {
  method(): void
}

// 下面這種做法會報錯
class C implements A {
  // 因為propA是類A的保護成員,只有自身和子類可實現
  // 但類C不是A的子類
  protected propA: string
  method() {}
}

// 下面這種做法則是允許的
class C extends A implements A {
  protected propA: string
  method() {}
}
複製程式碼

相關文章