打字稿:在 class 内扩展 "this"

Typescript: extending "this" inside class

我有一个 class Foo,我想用我拥有的一些文字对象动态扩展它。如何在不在单独的界面中手动重复文字对象的键 的情况下保持 this 键入

const someLiteralObject = {
  key1: 'foo',
  key2: 'bar',
  //  ...lots of more keys
}

class Foo {
   constructor(){
      Object.assign(this, someLiteralObject)
   }
   test() {
    console.log(this.key1); //error: Property 'key1' does not exist on type 'Foo'
   }
}

const test = new Foo();
console.log(test.key1); //error: Property 'key1' does not exist on type 'Foo'

强类型方式:

class Some { // <= This is representing your Object
        key1: string;
        key2: string;
}

const someLiteralObject: Some = { // <= As you can see it is of type Some
  key1: 'foo',
  key2: 'bar',
}

class Foo extends Some { // <= Tell TS we are using all the keys from Some
   constructor(){
      super(); // <= needed for ts, useless as we did not define a constructor in Some
      Object.assign(this, someLiteralObject);
   }
   test() {
    console.log(this.key1); // Yeah!
   }
}

const test = new Foo();
console.log(test.key1); // No error!
test.test(); // works fine!

Hacky 方式(例如,当您不知道对象将具有哪些键时)

const someLiteralObject = {
  key1: 'foo',
  key2: 'bar',
}

class Foo {
   constructor(){
      Object.assign(this, someLiteralObject)
   }
   test() {
    console.log((this as any).key1); // tell ts to be unsure about the type of this
   }
}

const test = new Foo();
console.log((test as any).key1); // tell ts to be unsure about the type of this

编辑:我建议您查看 Mixins in Typescript 并研究那里提供的 Object.assignapplyMixins 助手之间的区别。也许那个设计模式才是你真正需要的。

这是一个肮脏但强类型的解决方案,它使用文字对象而不是按照要求重复所有键。内联解释和 TypeScript Playground link.

// Hide these helpers away in your `utils.ts` somewhere, see below what they do.
/**
 * Gives Constructor given a instance, like inverse of `InstanceType`.
 *
 * Second optional parameter is argument tuple for the `constructor()`.
 *
 * @todo this interface lacks signature for static methods/properties.
 */
export interface IConstructor<T extends object = object, TA extends unknown[] = unknown[]> {
    new(...args: TA): T
}
/**
 * Overrrides a class to return a instance that includes the given mixin.
 */
export type ClassWithMixin<T extends IConstructor, TMixin extends object> =
    IConstructor<InstanceType<T> & TMixin, ConstructorParameters<T>>


// A mixin many keys and/or methods. Use `typeof someLiteralObject` to use it as interface.
const someLiteralObject = {
  key1: 'foo',
  key2: 'bar',
} as const  // <-- `as const` is optional, but handy.

// `this:` type tells method internal scope that `this` is more than TS thinks by default.
class FooPure {
   constructor(){
      Object.assign(this, someLiteralObject)
   }
   test(this: this & typeof someLiteralObject) {
    console.log(this.key1)
   }
}
// And `ClassWithMixin` type tells all other codebase that `Foo` is more than `FooHidden`.
const Foo = FooPure as ClassWithMixin<typeof FooPure, typeof someLiteralObject>

// Works as expected.
const foo = new Foo()
console.log(foo.key1)
foo.test()
class Bar extends Foo {
  constructor() {
    super()
    this.test()
  }
}
console.log(new Bar())