Typescript的interface、class和abstract class


interface,class,和abstract class這3個概念,既有聯系,又有區別,本文嘗試着結合官方文檔來闡述這三者之間的關系。

1. Declaration Merging

Declaration Type Namespace Type Value
Namespace X X
Class X X
Enum X X
Interface X
Type Alias X
Function X
Variable X

首先我們來講一下上面這張表格,當我們第一列的關鍵字進行聲明時,我們在做什么。

namespace job {
   haircut(): void;
}

class Man{
	name: string;
}
let imgss = new Man();

enum Color {red, blue, yellow}

interface dogfood {

  brand: string;
  price: number
}
type event = 'mouse' | 'keyboard';

function foo(){}

let a = 2;
var b = {};
const c = null;
	

namespace 用來聲明一個命名空間,比較著名的命名空間有lodash,里面有一堆工具函數,統統放在一個叫_的 namespace 里面,同時你也可以let $ = _;所以namespace也聲明了一個值。

class 聲明了一個值,也聲明了一種類型。你可以把Man賦值給一個變量,所以class是一種值,也可以說imgss是一個Man(類型),此時Man承擔了一種類型的角色。

enum聲明了一個值,也聲明了一種類型。我們說red是一種Color,Color在這里承擔類型的角色,也可以把Color賦值給一個變量

interface聲明了一種類型,但是你不能把dogfood賦值給某個變量,否則你會得到一個報錯``dogfood' only refers to a type, but is being used as a value here`

其他function,let,var,const都在聲明一個值,你 不能說xxx是一個a,或者xxx是一個foo,不能把值當成類型使用。

2. interface和class

我們知道,不算symbol,js中有6種基本類型,number,string,boolean,null, undefined, object。但是只依靠這幾種類型,來描述某個函數需要傳什么樣的參數,是遠遠不夠的,這也是interface的使命--描述一個值(value)的形狀(type)。

現在我們來看class,class首先也具有interface的能力,描述一個形狀,或者說代表一種類型。此外class還提供了實現,也就是說可以被實例化;

所以class可以implements interface:

interface ManLike {
  speak(): void;
  leg: number;
  hand: number;
}
class Human implements ManLike {
  leg: number = 2;
  hand: number = 2;
  speak() {
    console.log('i can speak');
  }
}

而interface可以extends class,此時的class承擔類型的角色

interface Chinese extends Human {
  country: string;
}

那么interface能不能extends enum或者type alias呢,這兩個兄弟也聲明了type啊,答案是不行的,官方報錯的信息:

An interface can only extend an object type or intersection of object types with statically known members.

interface 和 type alias

在大多數情況下,interface可以實現的,type alias也可以。官方文檔給出了兩者的區別,這邊翻譯一下,忽略掉報錯提示的區別,主要有以下兩點:

  1. type alias 可以重命名原始類型,interface不可以:
type Num = number;
// interface 不行
  1. interface可以進行declaration merging, type alias不可以:
interface Mammal {
    genus: string
}

interface Mammal {
    breed: string
}

// 前面兩個interface被合並成一個同時具有genus和breed屬性的類型
const animal: Mammal = {
    genus: "1234",
    // Fails because breed has to be a string
    breed: '2'
}

type Reptile = {
    genus: string
}

// type一旦被聲明,不能再加新屬性
type Reptile = {
    breed?: string
}

這會導致一個有趣的問題,這個問題在github上有人提出:

// -------- base --------

type Data = Record<string, string | number>;

function fn(data: Data) {}

// -------- step one --------

type IGetUserServiceList = {
  id: string;
};

let fooData: IGetUserServiceList = {
  id: '12345',
};

fn(fooData);

// -------- step two --------

interface _IGetUserServiceList {
  id: string;
}

let _fooData: _IGetUserServiceList = {
  id: '12345',
};

// error
// 類型“_IGetUserServiceList”中缺少索引簽名
fn(_fooData);

// 改為如下即可
interface __IGetUserServiceList {
  // 需要增加索引簽名
  [k: string]: string | number;
  id: string;
}

原因就是interface可以多次聲明,可以被declaretion merging,__IGetUserServiceList 加入索引簽名之后,可以將interface后續加入的屬性約束在一個范圍內 [k: string]: string | number,保證__IGetUserServiceList符合Data的shape。

3. class和abstract class

class和abstract class的區別主要是abstract class不能被實例化:

abstract Human {
	name: string;
    abstract lang(): void;
	toString() {
    	return `<human:${this.name}>`
    }
}
new Human // Cannot create an instance of an abstract class.

4. interface和abstract class

兩者都不能被實例化,但是abstract class 也可以被賦值給變量。
interface 里面不能有方法的實現,abstract class 可以提供部分的方法實現,這些方法可以被子類調用。

參考: https://www.typescriptlang.org/docs/handbook/declaration-merging.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM