键入一个使用静态方法导出函数构造函数的 CommonJS 模块

Typing a CommonJS module that exports a function constructor with static methods

我正在尝试为 minimal-bit-array 创建声明文件。这是它的本质:

function A() { this.data = 'foo'; }

A.prototype.serialize = function() { /* ... */ };

module.exports = A;
module.exports.deserialize = function(x) { /* ... */ };

我该如何输入?

是这样的:

declare module "a" {
  namespace A {
    export interface X { /* ... */ }
  }

  interface A {
    data: string;
    serialize(): A.X;
  }

  const A: {
    new (): A;
    deserialize(obj: A.X): A;
  };

  export = A;
}

这个反直觉的 namespace + interface + const 三明治被成功地合并到 TypeScript 可以理解的明确 东西 中。消费示例:

import * as A from "a";

const a: A         = new A();
const d: A['data'] = a.data;
const x: A.X       = a.serialize();
const amazing      = A.deserialize(x);

为什么是三明治?

理论上,namespace Aconst A在做同样的事情:描述amodule.exports。但是构造函数 - new (): A - 只能在接口中声明,而 TypeScript-only 的东西 - interface X - 只能在命名空间中定义。这就是同时需要两者的原因。否则无法导出 interface X。至少据我所知。值得庆幸的是,TypeScript 知道如何合并它们。

与此同时,interface A只是一个方便。当在需要类型的上下文中的代码中某处引用 A 时,这就是您获得的接口。另外两个 - namespaceconst - 不是类型,所以没有歧义。可以将其保留在 namespace A 中,但是,如果这太神奇了,请将其引用为 A.A.

在 2.8 上测试。

更新

一种更简洁的方法:

declare module "a" {
  namespace A {
    export interface X { /* ... */ }
  }

  class A {
    static deserialize(obj: A.X): A;
    data: string;
    constructor();
    serialize(): A.X;
  }

  export = A;
}

类 正好适合这种情况。这在语义上也等同于原始方法,因为 classes 本质上是函数之上的语法糖。检查 this playground 以查看上述 class 的实现将被转换成什么。

在 2.8 上测试。