使用TypeScript在接口中定義靜態(tài)方法詳解
靜態(tài)方法
靜態(tài)方法或靜態(tài)屬性是存在于類的任何實例中的屬性,它們是在構(gòu)造函數(shù)級別定義的,也就是說,類本身具有這些方法,因此這些類的所有實例也將具有這些方法。
例如,當(dāng)我們創(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í)行類似以下的標(biāo)準(zhǔn)操作
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ū)分。
但是,當(dāng)我們需要使用靜態(tài)類型對動態(tài)語言進行類型化時,會發(fā)生什么情況呢?
在 TypeScript 中,當(dāng)我們嘗試聲明一個類有動態(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
,這也行得通,但當(dāng)我們需要從一個對象創(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)在,當(dāng)我們使用這種類型的數(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-02js 關(guān)于=+與+=日期函數(shù)使用說明(賦值運算符)
js 關(guān)于=+與+=日期函數(shù)使用說明(賦值運算符),可以看下,就是一些運算符的使用,看哪個更適合你。2011-11-11JS對象序列化成json數(shù)據(jù)和json數(shù)據(jù)轉(zhuǎn)化為JS對象的代碼
這篇文章主要介紹了JS對象序列化成json數(shù)據(jù)和json數(shù)據(jù)轉(zhuǎn)化為JS對象的代碼,需要的朋友可以參考下2017-08-08Javascript基于jQuery UI實現(xiàn)選中區(qū)域拖拽效果
這篇文章主要介紹了Javascript基于jQuery UI實現(xiàn)選中區(qū)域拖拽效果的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-11-11javascript中的window.location.search方法簡介
window.location.search方法是截取當(dāng)前url中“?”后面的字符串,示例如下,感興趣的朋友可以參考下2013-09-09JavaScript判斷變量是否為undefined的兩種寫法區(qū)別
這篇文章主要是對JavaScript判斷變量是否為undefined的兩種寫法區(qū)別進行了詳細的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12