欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用TypeScript在接口中定義靜態(tài)方法詳解

 更新時間:2023年10月31日 11:38:38   作者:it鍵盤俠  
當(dāng)我們談?wù)撁嫦驅(qū)ο缶幊虝r,最難理解的事情之一就是靜態(tài)屬性與實例屬性的概念,尤其是當(dāng)我們試圖在靜態(tài)類型的基礎(chǔ)上進行動態(tài)語言類型化時,在本文中,我將主要介紹一下如何使用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)文章

最新評論