TypeScript程式語言的介面,是TypeScript特有、JavaScript沒有的東西,它與declare
關鍵字類似,只是用來寫給編譯器看的。TypeScript的介面可以將物件型別抽像化並加上名稱,形成新的型別,還能讓類別根據介面來添加屬性。
還記得在上一章中,我們是怎麼撰寫物件的型別嗎?
用以下這個大括號{}
物件為例:
const o = {
name: "David",
age: 18,
toString: function () {
return `${this.age}: ${this.name}`;
},
};
它的型別就是{ name: string, age: number, toString: () => string }
。所以我們可以用以下的方式來明確定義變數o
的型別:
const o: { name: string, age: number, toString: () => string } = {
name: "David",
age: 18,
toString: function () {
return `${this.age}: ${this.name}`;
},
};
我們可以透過TypeScript的介面,將{ name: string, age: number, toString: () => string }
這個型別加上名稱。例如要將其命名為User
,程式如下:
interface User {
name: string,
age: number,
toString: () => string
}
const o: User = {
name: "David",
age: 18,
toString: function () {
return `${this.age}: ${this.name}`;
},
};
如何?有沒有覺得程式更容易閱讀了?
用類別物件實作介面
我們還可以在撰寫類別物件的時候,於類別名稱後面使用implements
關鍵字來指定要實作的介面。介面擁有的屬性,除了可選的屬性之外,實作該介面的類別也都要有。
例如:
interface User {
name: string,
age: number,
toString: () => string
}
class Student implements User {
name: string;
age: number;
toString: () => string;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.toString = () => `${this.age}: ${this.name}`;
}
}
我們無法用instanceof
關鍵字來判斷某個值是否屬於指定的介面。例如以下程式會編譯錯誤:
interface User {
name: string,
age: number,
toString: () => string
}
class Student implements User {
name: string;
age: number;
toString: () => string;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.toString = () => `${this.age}: ${this.name}`;
}
}
const o = new Student("David", 18);
console.log(o instanceof User);
implements
關鍵字和extends
關鍵字可以一同使用,但是implements
關鍵字必須要放在extends
關鍵字之後。
例如:
class User {
name: string;
protected type = "user";
constructor(name: string) {
this.name = name;
}
whoAmI() {
return `My name is ${this.name}. I am a ${this.type}.`;
}
}
class Human extends User {
constructor(name: string) {
super(name);
this.type = "human";
}
}
class Robot extends User {
constructor(name: string) {
super(name);
this.type = "robot";
}
}
interface Studiable {
study: () => void
}
class Student extends Human implements Studiable {
study = () => {
console.log("I'm studying!");
};
constructor(name: string) {
super(name);
this.type = "student";
}
}
extends
關鍵字只能繼承一個類別。implements
關鍵字則可以用來實作多個介面,用逗號,
隔開多個介面名稱。
例如:
class User {
name: string;
protected type = "user";
constructor(name: string) {
this.name = name;
}
whoAmI() {
return `My name is ${this.name}. I am a ${this.type}.`;
}
}
class Human extends User {
constructor(name: string) {
super(name);
this.type = "human";
}
}
class Robot extends User {
constructor(name: string) {
super(name);
this.type = "robot";
}
}
interface Studiable {
study: () => void
}
interface Expellable {
expel: () => void
}
class Student extends Human implements Studiable, Expellable {
study = () => {
console.log("I'm studying!");
};
expel = () => {
console.log("Noooo!");
};
constructor(name: string) {
super(name);
this.type = "student";
}
}
擴充介面
藉由重複撰寫interface
關鍵字,可以替已存在的介面擴充原本不存在的屬性。
例如:
interface Point {
x: number,
y: number
}
interface Point {
z: number
}
class Vertex implements Point {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
}
以上程式,Point
介面實際為:
interface Point {
x: number,
y: number,
z: string
}
介面繼承介面
介面也可以使用extends
關鍵字來繼承其它介面。這功能其實就是剛才提到的「擴充介面」,只不過多用了extends
關鍵字來保留原本的介面名稱。
例如:
interface Point2D {
x: number,
y: number
}
interface Point3D extends Point2D {
z: number
}
class Vertex implements Point3D {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
}
與類別不同的是,類別的extends
關鍵字後只能接上一個類別名稱,而介面的extends
關鍵字後可以接上多個介面名稱或是類別名稱,要用逗號,
隔開。
例如:
interface PointX {
x: number
}
interface PointY {
y: number
}
interface PointZ {
z: number
}
interface Point2D extends PointX, PointY {
}
interface Point3D extends Point2D, PointZ {
}
class Vertex implements Point3D {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
}
介面和類別的名稱重複
在TypeScript中,介面和類別的名稱是可以重複的。重複名稱的介面會擴充類別的屬性,但類別可以不必照著這個重複名稱的介面來實作程式。
例如:
interface Student {
dropOut: () => void
}
class Student {
name: string;
age: number;
toString: () => string;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.toString = () => `${this.age}: ${this.name}`;
}
}
const o = new Student("David", 18);
o.dropOut();
以上程式雖然可以成功編譯,但會執行失敗,因為Student
物件實體並沒有dropOut
方法。
抽象類別(Abstract Class)
在定義一個類別的時候,可以在其class
關鍵字前加上abstract
,使其變成抽象類別。TypeScript的抽象類別和一般類別的差異不大,只不過就是不能夠被「new」而已。
例如:
interface PointX {
x: number
}
interface PointY {
y: number
}
interface PointZ {
z: number
}
interface Point2D extends PointX, PointY {
}
interface Point3D extends Point2D, PointZ {
}
abstract class AbstractVertex implements Point3D {
x: number;
y: number;
z: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
}
class Vertex extends AbstractVertex {
constructor(public x: number, public y: number, public z: number) {
super(x, y, z);
}
}
以上的AbstractVertex
類別是一個抽象類別,它不能夠被用new
關鍵字來產生物件實體,但是它可以被繼承,也可以透過其子類別Vertex
的建構子來呼叫到AbstractVertex
類別的建構子。
我們也可以在抽象類別的屬性(欄位或是方法都可以)前加上abstract
關鍵字來修飾,使得繼承這個抽象類別的類別必須要實作這個屬性。使用abstract
關鍵字來修飾的屬性,不需指定值,也不需實作出函數主體,只要把名稱和型別定義出來就好了。
例如:
abstract class Animal {
abstract readonly name: string;
abstract makeSound(): void;
move(): void {
console.log("roaming the earth...");
}
}
class Dog extends Animal {
readonly name = "Dog";
makeSound(): void {
console.log("Woof!");
}
}
總結
在這個章節中我們學會了TypeScript提供的介面與抽象類別的用法,在下一個章節要來學習TypeScript提供的列舉用法。
下一章:TypeScript程式語言的列舉。