Typescript:通过this +方法名调用方法

bvjveswy  于 2022-11-18  发布在  TypeScript
关注(0)|答案(4)|浏览(254)

我正在将一个项目转换成Typsescript。这是一个艰难的旅程,但我正在一步一步地做到这一点。但有一件事一直困扰着我。
我可以在纯Javascript中执行此操作:

class Example {
  callThis(methodName) {
    if (typeof this[methodName] === "function"){
      this[methodName]();
    }
  }

  getSomeText() {
    return this.someText;
  }

  setSomeText(someText) {
    this.someText = someText;
  }

  myMethod() {
    console.log(this.getSomeText());
  }

  myOtherMethod() {
    //etc
  }

  //and more methods that can be called with callThis()
}

const aTest = new Example;
aTest.setSomeText("calling myMethod()");
aTest.callThis("myMethod");

这个函数运行良好,并输出“calling myMethod()"。当我将它转换为Typescript时,我在callThis方法上得到一个错误。它告诉我:
元素隐式具有“any”类型,因为类型“string”的表达式不能用于索引类型“Example”。在类型“Example”上找不到参数类型为“string”的索引签名。
我怎样才能让TypeScript接受这个设置呢?我稍后会添加新的可调用方法,不仅仅是这个,所以我希望尽可能灵活。我已经创建了一些新类型,但在这个阶段我还是个婴儿,所以请耐心等待。
这背后的整体想法是,类别是用来从HTML <template>节点建立对话方块,而callThis函式则是用来在节点加入DOM之后,将事件侦听程式加入其中。

pepwfjgg

pepwfjgg1#

解决方案是重写调用该类的代码,从而避免动态调用方法。在尝试了建议的代码之后,我的收获是:不要尝试在TypeScript中动态调用方法,没有简单的方法可以做到这一点。

6psbrbz9

6psbrbz92#

我不确定这个答案是否有用,但我上过这样的课

class MakeViews {
  offset = 0;

  constructor(arrayBuffer: ArrayBuffer) {
    this.arrayBuffer = arrayBuffer;
  }
  getView<TypedArrayConstructor>(Ctor: TypedArrayConstructor, size: number) {
     const view = new TypedArrayConstructor(this.arrayBuffer, this.offset, size);
     this.offset += view.byteLength;
     return view;
  }
  i8(size: number) { return getView(Int8Array, size); }
  u8(size: number) { return getView(Uint8Array, size); }
  i16(size: number) { return getView(Int16Array, size); }
  u16(size: number) { return getView(Uint16Array, size); }
  i32(size: number) { return getView(Int32Array, size); }
  u32(size: number) { return getView(Uint32Array, size); }
  f32(size: number) { return getView(Float32Array, size); }
  f64(size: number) { return getView(Float64Array, size); }
  ....
}

我有一段代码根据结构定义调用它

const structDef = {
   color: { type: 'u8', size: 4, },
   foo: { type: 'i8', size: 1, },
};

const views = {};
const m = new MakeViews(5);
for (const [key, {type, size}] of structDef) {
   views[key] = m[type](size);  // <---- Typescript doesn't like this
}

我知道这不是严格意义上的“类型正确”,但它确实有效。无论如何,我尝试了一堆类型友好的方法。我惊讶地发现,各种类型正确的方法都有similar performance
下面是我的无意义的例子。抱歉命名不好。
当然,每个浏览器都有自己的优势。而且,哪个方法获胜取决于每个类调用函数的次数,至少在这些测试中是这样。例如,在Chrome 107中,如果每个类调用函数1000次,那么按名称排序的函数获胜。如果每个类调用函数的次数比原来的多16次,非typescript友好的方法获胜,但是第二个方法,命名函数的对象,并没有那么慢,~10%,当然,如果函数不仅仅是加法和乘法,那么10%会更低。对于较小的使用,非箭头函数的解决方案会变得更慢,因为它必须为每个类示例创建新的闭包,这是有道理的,但它还没有坏到我会担心它。
另外,我测试的是JavaScript,而不是 typescript 。我不知道 typescript 的翻译是否会增加额外的开销。

class ByClass {
  #count = 0;
  by2(v: number) {
    this.#count += v * 2;
  }
  by4(v: number) {
    this.#count += v * 4;
  }
  by8(v: number) {
    this.#count += v * 8;
  }
  by16(v: number) {
    this.#count += v * 16;
  }
  add(name: string, v: number) {
    this[name](v);   // <-- problematic (not type safe)
  }
  getResult() {
      return this.#count;
  }
}

class ByClassFn {
  count = 0;
  fns = {
    by2(v: number) { this.count += v * 2 },
    by4(v: number) { this.count += v * 4 },
    by8(v: number) { this.count += v * 8 },
    by16(v: number) { this.count += v * 16 },
  };
  add(name: string, v: number) {
    this.fns[name].call(this, v);
  }
  getResult() {
    return this.count;
  }
}

class ByClassArrowFn {
  count = 0;
  fns = {
    by2: (v: number) => { this.count += v * 2 },
    by4: (v: number) => { this.count += v * 4 },
    by8: (v: number) => { this.count += v * 8 },
    by16: (v: number) => { this.count += v * 16 },
  };
  add(name: string, v: number) {
    this.fns[name](v);
  }
  getResult() {
    return this.count;
  }
}

class ByClassStaticFn {
  count = 0;
  static fns = {
    by2(v: number) { this.count += v * 2 },
    by4(v: number) { this.count += v * 4 },
    by8(v: number) { this.count += v * 8 },
    by16(v: number) { this.count += v * 16 },
  };

  add(name: string, v: number) {
    ByClassStaticFn.fns[name].call(this, v);
  }
  getResult() {
    return this.count;
  }
}

class ByClassStaticMapFn {
  count = 0;
  static fns = new Map<string, (n: number) => void>([
    [ 'by2', function(v: number) { this.count += v * 2 }, ],
    [ 'by4', function(v: number) { this.count += v * 4 }, ],
    [ 'by8', function(v: number) { this.count += v * 8 }, ],
    [ 'by16', function(v: number) { this.count += v * 16 }, ],
  ]);

  add(name: string, v: number) {
    ByClassStaticMapFn.fns.get(name)!.call(this, v);
  }
  getResult() {
    return this.count;
  }
}
ewm0tg9j

ewm0tg9j3#

为了将其转换为 typescript ,您需要编写自定义的打字保护:

const withoutParams = (fn: (...args: any) => any): fn is () => void =>
    fn.length === 0

const isFunction = (fn: any): fn is () => void =>
    typeof fn === 'function'

class Example {
    someText?: string;

    callThis<T,>(this: T, methodName: keyof T) {
        const x = this[methodName]
        if (isFunction(x) && withoutParams(x)) {
            x();
        }
    }

    getSomeText() {
        return this.someText;
    }

    setSomeText(someText: string) {
        this.someText = someText;
    }

    myMethod() {
        console.log(this.getSomeText());
    }

    myOtherMethod() {
    }

}

const aTest = new Example;
aTest.setSomeText("calling myMethod()");
aTest.callThis("myMethod");

如果你想调用this[methodName],首先你需要向TS保证它是一个函数,然后你需要向TS保证这个函数没有参数。
另外,您需要告诉TS您可能有someText属性
Playground

x33g5p2x

x33g5p2x4#

这样的东西对我很有效。我不知道你要怎么传递参数...

export class Example {
  private my_text = '';
  callThis(member: keyof Example): void {
    this[member](member);
  }
  getSomeText(): void {
    this.my_text = 'getSomeText';
  }

  setSomeText(): void {
    this.my_text = 'setSomeText';
  }
}

然后在index.ts中

import { Example } from './Example';

const lo_example = new Example();
lo_example.callThis('getSomeText');

相关问题