如何获取从 Class 实例克隆的对象的类型?

How do you get the type of the object that is cloned from a Class Instance?

假设我有这个例子class,但实际上它有更多的属性

class Foo {
  name: string
  dob: number

  constructor(name: string, dob: number) {
    this.name = name;
    this.dob = dob;
  }

  get age() {
     return new Date().getTime() - this.dob
  }
}

现在 Typescript 很智能,当我实例化 class 时,它会给我所有正确的属性:

var classInstance = new Foo('name', new Date().getTime())

classInstance.name // ok
classInstance.dob // ok
classInstance.age // ok

在我的代码中的某个地方,class 使用传播运算符被克隆,我不确定 TS 在幕后做了什么,但它真的很聪明并为我提供了所有正确的属性

var classJSON = {...classInstance};

classJSON.name // ok
classJSON.dob // ok
classJSON.age // missing

tsplayground

这很好,但是我有时需要使用 classJSON 的类型。我认为提取它的唯一方法是这样做:

var classJSON  = {...new Foo('', 0)}
type ClassJSONType = typeof classJSON; 

有没有办法直接从 Foo 中提取类型而不需要 Javascript 实例化?

目前不可能,因为 TS 类型系统不允许跟踪非自有属性。 有一个 proposal for exactly what you want. This comment 也描述了在不剥离非自有属性的情况下表达 Spread 运算符的可能方法。

免责声明:此 post 是基于 的 mod。固方案,纯TS,无任何JS运行时影响。

type IfEquals<X, Y, T> =
    (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? T : never;

type JSONify<T> = Pick<T, {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T]>;

以上技巧排除了所有 readonly 字段。要进一步排除方法,请使用以下内容:

type JSONify<T> = Pick<T, {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] extends Function ? never : T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T]>;

Playground

如果您只想避免初始化 Foo,您可以这样做:

var funRetClassJSON = () => ({ ...Foo.prototype });
type TypeOfClassJSON = ReturnType<typeof funRetClassJSON>;

var nullClassJSON = false as any ? null : ({ ...Foo.prototype });
type AltTypeOfClassJSON = NonNullable<typeof nullClassJSON>;

当然,这两个示例仍然会产生一些运行时损失,因为仍然会生成一些 JS。

如果您希望该类型在克隆时保持不变,那么您可能需要使用类型断言。

var classJSON = {...classInstance} as Foo;

这将直接通知 Typescript 对象的类型应该是什么。如果没有此注释,可能无法使用扩展运算符进行类型推断。