使用TypeScript在接口中定義靜態(tài)方法詳解
靜態(tài)方法
靜態(tài)方法或靜態(tài)屬性是存在于類的任何實例中的屬性,它們是在構(gòu)造函數(shù)級別定義的,也就是說,類本身具有這些方法,因此這些類的所有實例也將具有這些方法。
例如,當我們創(chuàng)建一個域?qū)ο蠡驍?shù)據(jù)庫實體時,就會用到常見的靜態(tài)方法:
class Person {
static fromObject (obj: Record<string, unknown>) {
const instance = new Person()
instance.prop = obj.prop
return instance
}
toObject () {
return {
prop: this.prop
}
}
fromObject 方法存在于所有類中,它位于任何實例之上,因此不能使用 this 關(guān)鍵字,因為 this 尚未初始化,而且你所處的上下文高于 this 可以引用的任何實例。
在本例中,我們接收了一個對象,并直接用它創(chuàng)建了一個新的類實例。要執(zhí)行這段代碼,請不要執(zhí)行類似以下的標準操作
const p = new Person() p.fromObject(etc) // error, the property does not exist in the instance
我們需要直接從類的構(gòu)造函數(shù)中調(diào)用該方法:
const p = Person.fromObject(etc)
引出的問題
靜態(tài)方法在強類型語言中非常常見,因為類的靜態(tài)時刻和 "動態(tài) "時刻之間有明確的區(qū)分。
但是,當我們需要使用靜態(tài)類型對動態(tài)語言進行類型化時,會發(fā)生什么情況呢?
在 TypeScript 中,當我們嘗試聲明一個類有動態(tài)方法和靜態(tài)方法,并嘗試在接口中描述這兩種方法時,就會出現(xiàn)一些錯誤:
interface Serializable {
fromObject (obj: Record<string, unknown>): Person
toObject (): Record<string, unknown>
}
class Person implements Serializable
// Class 'Person' incorrectly implements interface 'Serializable'.
// Property 'fromObject' is missing in type 'Person' but required in type
// 'Serializable'.
出現(xiàn)這種情況的原因是,TypeScript 中的接口作用于類的 dynamic side(動態(tài)端),因此就好像所有接口都是相關(guān)類的實例,而不是類本身。
幸運的是,TypeScript 提供了一種將類聲明為構(gòu)造函數(shù)的方法,即所謂的構(gòu)造函數(shù)簽名(Constructor Signatures):
interface Serializable {
new (...args: any[]): any
fromObject(obj: Record<string, unknown>): Person
toObject(): Record<string, unknown>
}
現(xiàn)在應(yīng)該能用了吧?遺憾的是,即使你手動實現(xiàn)了該方法,該類仍然會說你沒有實現(xiàn) fromObject 方法。
靜態(tài)反射問題
例如,如果我們想創(chuàng)建一個數(shù)據(jù)庫類,直接使用類中的實體名稱來創(chuàng)建文件,這可以通過任何類中的 name 屬性來實現(xiàn),這是一個靜態(tài)屬性,存在于所有可實例化的對象中:
interface Serializable {
toObject(): any
}
class DB {
constructor(entity: Serializable) {
const path = entity.name // name does not exist in the property
}
}
好了,我們可以將 entity.name 替換為 entity.constructor.name,這也行得通,但當我們需要從一個對象創(chuàng)建一個新實體時怎么辦呢?
interface Serializable {
toObject(): any
}
class DB {
#entity: Serializable
constructor(entity: Serializable) {
const path = entity.constructor.name
this.#entity = entity
}
readFromFile() {
// we read from this file here
const object = 'file content as an object'
return this.#entity.fromObject(object) // fromObject does not exist
}
}
因此,我們有一個選擇:要么優(yōu)先處理實例,要么優(yōu)先處理構(gòu)造函數(shù)...
解決方案
幸運的是,我們有辦法解決這個問題。我們定義接口的兩部分,即靜態(tài)部分和實例部分:
export interface SerializableStatic {
new (...args: any[]): any
fromObject(data: Record<string, unknown>): InstanceType<this>
}
export interface Serializable {
id: string
toJSON(): string
}
需要注意的是,in 中的構(gòu)造函數(shù)的類型
new(...args: any[]): any必須與 return 中的類型相同any,否則就會成為循環(huán)引用
有了類的這兩部分類型,我們可以說類只實現(xiàn)了實例部分:
class Person implements Serializable {
// ...
}
現(xiàn)在,我們可以說我們的數(shù)據(jù)庫將接收兩種類型的參數(shù),一種是靜態(tài)部分,我們稱之為 S,另一個是動態(tài)(或?qū)嵗┎糠?,我們稱之為 I,S 將始終擴展 SerializableStatic而 I 將始終擴展 Serializable,默認情況下,它將是 S 的實例類型,可以通過 InstanceType<S>類型使用程序來定義:
class Database<S extends SerializableStatic, I extends Serializable = InstanceType<S>> {}
例如,現(xiàn)在我們可以正常使用我們的屬性:
class Database<S extends SerializableStatic, I extends Serializable = InstanceType<S>> {
#dbPath: string
#data: Map<string, I> = new Map()
#entity: S
constructor(entity: S) {
this.#dbPath = resolve(dirname(import.meta.url), `.data/${entity.name.toLowerCase()}.json`)
this.#entity = entity
this.#initialize()
}
}
在 #initialize 方法中,我們將使用 fromObject 方法直接讀取文件,并將其轉(zhuǎn)化為一個類的實例:
class Database<S extends SerializableStatic, I extends Serializable = InstanceType<S>> {
#dbPath: string
#data: Map<string, I> = new Map()
#entity: S
constructor(entity: S) {
this.#dbPath = resolve(dirname(import.meta.url), `.data/${entity.name.toLowerCase()}.json`)
this.#entity = entity
this.#initialize()
}
#initialize() {
if (existsSync(this.#dbPath)) {
const data: [string, Record<string, unknown>][] = JSON.parse(readFileSync(this.#dbPath, 'utf-8'))
for (const [key, value] of data) {
this.#data.set(key, this.#entity.fromObject(value))
}
return
}
this.#updateFile()
}
}
此外,我們還可以使用 get 和 getAll 等方法,甚至是只接收和返回實例的保存方法。
get(id: string): I | undefined {
return this.#data.get(id)
}
getAll(): I[] {
return [...this.#data.values()]
}
save(entity: I): this {
this.#data.set(entity.id, entity)
return this.#updateFile()
}
現(xiàn)在,當我們使用這種類型的數(shù)據(jù)庫時,例如
class Person implements Serializable {
// enter code here
}
const db = new DB(Person)
const all = db.getAll() // Person[]
const oneOrNone = db.get(1) // Person | undefined
db.save(new Person()) // DB<Person>
以上就是使用TypeScript在接口中定義靜態(tài)方法詳解的詳細內(nèi)容,更多關(guān)于TypeScript定義靜態(tài)方法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于javascript、ajax、memcache和PHP實現(xiàn)的簡易在線聊天室
這篇文章主要介紹了基于javascript、ajax、memcache和PHP實現(xiàn)的簡易在線聊天室,需要的朋友可以參考下2015-02-02
js 關(guān)于=+與+=日期函數(shù)使用說明(賦值運算符)
js 關(guān)于=+與+=日期函數(shù)使用說明(賦值運算符),可以看下,就是一些運算符的使用,看哪個更適合你。2011-11-11
JS對象序列化成json數(shù)據(jù)和json數(shù)據(jù)轉(zhuǎn)化為JS對象的代碼
這篇文章主要介紹了JS對象序列化成json數(shù)據(jù)和json數(shù)據(jù)轉(zhuǎn)化為JS對象的代碼,需要的朋友可以參考下2017-08-08
Javascript基于jQuery UI實現(xiàn)選中區(qū)域拖拽效果
這篇文章主要介紹了Javascript基于jQuery UI實現(xiàn)選中區(qū)域拖拽效果的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11
javascript中的window.location.search方法簡介
window.location.search方法是截取當前url中“?”后面的字符串,示例如下,感興趣的朋友可以參考下2013-09-09
JavaScript判斷變量是否為undefined的兩種寫法區(qū)別
這篇文章主要是對JavaScript判斷變量是否為undefined的兩種寫法區(qū)別進行了詳細的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12

