使用TypeScript在接口中定义静态方法详解

 更新时间:2023年10月31日 11:38:38   作者:it键盘侠  
当我们谈论面向对象编程时,最难理解的事情之一就是静态属性与实例属性的概念,尤其是当我们试图在静态类型的基础上进行动态语言类型化时,在本文中,我将主要介绍一下如何使用TypeScript在接口中定义静态方法,需要的朋友可以参考下

静态方法

静态方法或静态属性是存在于类的任何实例中的属性,它们是在构造函数级别定义的,也就是说,类本身具有这些方法,因此这些类的所有实例也将具有这些方法。

例如,当我们创建一个域对象或数据库实体时,就会用到常见的静态方法:

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 关键字,因为 this 尚未初始化,而且你所处的上下文高于 this 可以引用的任何实例。

在本例中,我们接收了一个对象,并直接用它创建了一个新的类实例。要执行这段代码,请不要执行类似以下的标准操作

const p = new Person()
p.fromObject(etc) // error, the property does not exist in the instance

我们需要直接从类的构造函数中调用该方法:

const p = Person.fromObject(etc)

引出的问题

静态方法在强类型语言中非常常见,因为类的静态时刻和 "动态 "时刻之间有明确的区分。

但是,当我们需要使用静态类型对动态语言进行类型化时,会发生什么情况呢?

在 TypeScript 中,当我们尝试声明一个类有动态方法和静态方法,并尝试在接口中描述这两种方法时,就会出现一些错误:

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'.

出现这种情况的原因是,TypeScript 中的接口作用于类的 dynamic side(动态端),因此就好像所有接口都是相关类的实例,而不是类本身。

幸运的是,TypeScript 提供了一种将类声明为构造函数的方法,即所谓的构造函数签名(Constructor Signatures):

interface Serializable {
  new (...args: any[]): any
  fromObject(obj: Record<string, unknown>): Person
  toObject(): Record<string, unknown>
}

现在应该能用了吧?遗憾的是,即使你手动实现了该方法,该类仍然会说你没有实现 fromObject 方法。

静态反射问题

例如,如果我们想创建一个数据库类,直接使用类中的实体名称来创建文件,这可以通过任何类中的 name 属性来实现,这是一个静态属性,存在于所有可实例化的对象中:

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,这也行得通,但当我们需要从一个对象创建一个新实体时怎么办呢?

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
  }
}

因此,我们有一个选择:要么优先处理实例,要么优先处理构造函数...

解决方案

幸运的是,我们有办法解决这个问题。我们定义接口的两部分,即静态部分和实例部分:

export interface SerializableStatic {
  new (...args: any[]): any
  fromObject(data: Record<string, unknown>): InstanceType<this>
}

export interface Serializable {
  id: string
  toJSON(): string
}

需要注意的是,in 中的构造函数的类型new(...args: any[]): any 必须与 return 中的类型相同any,否则就会成为循环引用

有了类的这两部分类型,我们可以说类只实现了实例部分:

class Person implements Serializable {
  // ...
}

现在,我们可以说我们的数据库将接收两种类型的参数,一种是静态部分,我们称之为 S,另一个是动态(或实例)部分,我们称之为 I,S 将始终扩展 SerializableStatic而 I 将始终扩展 Serializable,默认情况下,它将是 S 的实例类型,可以通过 InstanceType<S>类型使用程序来定义:

    
class Database<S extends SerializableStatic, I extends Serializable = InstanceType<S>> {}
    

例如,现在我们可以正常使用我们的属性:

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 方法直接读取文件,并将其转化为一个类的实例:

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()
}

现在,当我们使用这种类型的数据库时,例如

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在接口中定义静态方法详解的详细内容,更多关于TypeScript定义静态方法的资料请关注脚本之家其它相关文章!

相关文章

  • yepnope.js使用详解及示例分享

    yepnope.js使用详解及示例分享

    yepnope.js 是一个超高速的按条件异步加载资源的加载器,允许你只加载使用到的资源(css及js)。
    2014-06-06
  • 详解vue-cli+es6引入es5写的js(两种方法)

    详解vue-cli+es6引入es5写的js(两种方法)

    本文通过两种方法给大家介绍vue-cli+es6引入es5写的js,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2019-04-04
  • 精通JavaScript的this关键字

    精通JavaScript的this关键字

    这篇文章主要介绍了JavaScript的this关键字,真正帮助大家做到精通this关键字,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • 如何编写一个 Webpack Loader的实现

    如何编写一个 Webpack Loader的实现

    这篇文章主要介绍了如何编写一个 Webpack Loader的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • jquery方法+js一般方法+js面向对象方法实现拖拽效果

    jquery方法+js一般方法+js面向对象方法实现拖拽效果

    多种方法制作的div拖拽,简单实用,包括了jquery方法、js一般方法、js面向对象方法
    2012-08-08
  • javascript导出csv文件(excel)的方法示例

    javascript导出csv文件(excel)的方法示例

    这篇文章主要给大家介绍了关于javascript导出csv文件(excel)的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用javascript具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-08-08
  • js省市联动效果完整实例代码

    js省市联动效果完整实例代码

    这篇文章主要介绍了js省市联动效果完整实例代码,涉及JavaScript数组的定义与遍历技巧,代码非常具有实用价值,需要的朋友可以参考下
    2015-12-12
  • javascript抽象工厂模式详细说明

    javascript抽象工厂模式详细说明

    这篇文章主要介绍了javascript抽象工厂模式详细说明,需要的朋友可以参考下
    2014-12-12
  • JavaScript中Number的对象解析

    JavaScript中Number的对象解析

    这篇文章主要介绍了JavaScript中Number的对象解析,Number对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用,感兴趣的朋友可以参考一下下面文章内容
    2022-08-08
  • 两个JavaScript中的特殊值null和undefined详解

    两个JavaScript中的特殊值null和undefined详解

    Null和Undefined是JavaScript中非常基础和重要的概念,理解它们的含义、特点和使用方式对于避免出现错误和编写健壮的应用程序非常重要,这篇文章主要介绍了两个JavaScript中的特殊值null和undefined详解,需要的朋友可以参考下
    2023-06-06

最新评论