动态类方法

时间:2017-09-16 21:49:55

标签: typescript

在使用动态方法扩展类时,是否有人对获取类型检查有一个很好的解决方法?例如,假设您希望使用基于传递给构造函数的选项的方法来扩展类。这在普通的“JavaScript”中很常见。

const defaults = {
  dynamicMethods: ['method1', 'method2'];
};

class Hello {
  constructor(options) {
    options.dynamicMethods.forEach(m => this[m] = this.common);
  }
  private common(...args: any[]) {
     // do something.
  }
}

const hello = new Hello(defaults);

当然上面的方法可行,你可以调用这些动态方法,但是你不会得到intellisense。

不是你可以通过以下方式解决这个问题:

class Hello<T> {
  constructor(options) {
    options.dynamicMethods.forEach(m => this[m] = this.common);
  }
  private common(...args: any[]) {
     // do something.
  }
}

interface IMethods {
  method1(...args: any[]);
  method2(...args: any[]);
}

function Factory<T>(options?): T & Hello<T> {
  const hello = new Hello<T>(options);
  return hello as T & Hello<T>;
}

消费这个:

import { Factory } from './some/path'
const hello = new Factory<IMethods>(defaults);

这当然有效,但想知道还有其他替代方案!

4 个答案:

答案 0 :(得分:3)

在玩了一下之后我想出了一些不需要为每个扩展声明一个接口的东西:

interface ClassOf<T> {
  new(...args: any[]) : T
}

const extendClass = <T,S>(class_ : ClassOf<T>, dynamicMethods : S) =>
  (...args: any[]) => {
    const o = new class_(args) as T & S;
    for (const key of Object.keys(dynamicMethods) as Array<keyof S>) {
      const method = dynamicMethods[key];
      (o as S)[key] = method; // type sig seems unnecessary
    }
  return o;
}

// demo:
class Hello {
  constructor(public name) {}

  hello() {
    return 'hello ' + this.name;
  }
}

const extHelloConstr = extendClass(Hello, {incr: (x : number) => x + 1, show: (n : number) => 'nr ' + n});
const extHello = extHelloConstr('jimmy');
const test1 = extHello.incr(1);
const test2 = extHello.show(42);
const test3 = extHello.hello();
console.log(test1, test2, test3);

playground link

除了构造函数参数(看起来很棘手)之外,所有推断类型都是正确的。它甚至可以在代码执行时起作用。你也可以返回一个匿名类,但输入它们有点奇怪。

不确定这是否是您正在寻找的东西,但也许它可以作为灵感来源。

答案 1 :(得分:1)

对于 ES6

有一个简单的方法,但是我不知道它是否好。我使用Object.assign方法:

class Test {
  constructor() {
    let dynamic_property_name = "hello_world";

    Object.assign(this, {
      [dynamic_property_name]: "new value"
    });
  }
}

console.log(new Test());

输出

Test { hello_world: 'new value' }

答案 2 :(得分:0)

从您的示例中,您可以摆脱IMethods界面并使用Record类型。

class Hello {
  constructor(options: string[]) {
    options.forEach(m => this[m] = this.common);
  }
  private common(...args: any[]) {
     // do something.
  }
}

function Factory<T extends string>(...options: T[]): Hello & Record<T, (...args) => any[]> {
  const hello = new Hello(options);
  return hello as Hello & Record<T, (...args) => any[]>;
}

const hello = Factory("method1", "method2");
hello.method1();
hello.method2();

答案 3 :(得分:0)

受到@Oblosys 回答的极大启发,但该解决方案还通过继承支持来自原始类的静态方法,避免了 any,并返回一个可构造的类而不是函数(不能用 {{ 1}} 在 TS)。它也可以无限扩展,所以你可以有许多不同的插件扩展一个记录器,然后仍然允许用户自己扩展它。

new
相关问题