简体   繁体   中英

Passing class method as parameter in Typescript

I'm searching for a possibility to pass a class-method to a function which then can execute that function on an instance of that class. Something like that pseudocode: (note that this is an abstract example)

class Foo {
    public somefunc() {
        // do some
    }
    public anyfunc() {
        // do any
    }
}

function bar(obj: Foo ,func: "Foo.method") {  // "that's what im looking for"
    obj.func();
}

bar(new Foo(), Foo.somefunc);  // do some
bar(new Foo(), Foo.anyfunc);  // do any

Is there a possiblity to do this?

I know i could be doing something like that:

class Foo {
    static somefunc(fooObj: Foo) {
        // do some
    }
    static anyfunc(fooObj: Foo) {
        // do any
    }
}

interface func {
    (fooObj: Foo);
}

function bar(obj: Foo, fn: func) {
    fn(obj);
}

bar(new Foo(), Foo.somefunc);  // do some
bar(new Foo(), Foo.anyfunc);  // do any

but that involves static functions which I don't want.

This doesn't compile-time check that the function came from a Foo , but does the rest:

class Foo {
    public somefunc() {
        // do some
    }
    public anyfunc() {
        // do any
    }
}

function bar(obj: Foo ,func: () => void) {
    func.call(obj);
}

bar(new Foo(), Foo.prototype.somefunc);  // do some
bar(new Foo(), Foo.prototype.anyfunc);  // do any

Typescript 2+ solution

TL;DR : TypeScript Playground , Repo with a demo

Advantages:

  1. Compile-time checking.
  2. Won't let you lost this context when passing an instance's method.
  3. Don't lose performance: don't have to declare class' methods as instance methods (eg public somefunc = () => { return this.prop; } ) - Learn more .
  4. Don't mess with a class's prototype.
  5. Consistent signature pattern: passing a callback as the first arg and thisArg as the second (eg Array.prototype.map() ).

Consider the following code:

class Foo {
    private result: number = 42;

    public func(this: Foo): number {
        return this.result;
    }
}

function action(): void {
    console.log("Hello world!");
}

function bar(callbackFn: (this: void) => any, thisArg?: undefined): any;
function bar<T>(callbackFn: (this: T) => any, thisArg: T): any;
function bar<T, TResult>(callbackFn: (this: T) => TResult, thisArg: T): TResult {
    return callbackFn.call(thisArg);
}

const foo = new Foo();

bar(action); // success
bar(foo.func); // ERROR: forgot to pass `thisArg`
bar(foo.func, foo); // success

Turn your attention to the signature of Foo#func :

public func(this: Foo): number

It states that this function should be invoked in a context of the class' instance. This is the first part of the solution which won't let you lost this context.

The second part is bar function overloads:

function bar(callbackFn: (this: void) => any, thisArg?: undefined): any;
function bar<T>(callbackFn: (this: T) => any, thisArg: T): any;
function bar<T, TResult>(callbackFn: (this: T) => TResult, thisArg: T): TResult

This would let you you pass generic functions as well as instance methods.

You can learn more about these topics in TypeScript Handbook:

  1. this parameters in callbacks
  2. Function overloads
  3. Generics

I'm assuming you're looking for some way for the TypeScript compiler to enforce that the given function exists on Foo? Unfortunately, I don't think there's a way to do that. Maybe another TypeScript guru can come in here and answer that more concretely, but I'm pretty sure this is the closest that you can get:

class Foo {
    constructor(private name:string) { }

    public somefunc() {
        console.log("someFunc called on", this.name);
    }
    public anyfunc() {
        console.log("anyFunc called on", this.name);
    }
}

function bar(obj: Foo, func: string) {
    if (obj[func] && obj[func] instanceof Function) {
        obj[func]();
    } else {
        throw new Error("Function '" + func + "' is not a valid function");
    }
}

bar(new Foo("foo1"), "somefunc");  // output: 'somefunc called on foo1'
bar(new Foo("foo2"), "anyfunc");  // output: 'anyfunc called on foo1'
bar(new Foo("foo3"), "badFunction");  // throws: Error: Function 'badFunction' is not a valid function

Yes, declare function like this:

myfunction(action: () => void){
   action();
}

Call it like this from typescript:

myfunction(() => alert("hello"));

Or from javascript:

myfunction(function() { alert("hello"); });

Also you can pass method:

myfunction(this.someMethod);

For my part; according to the statement of the problem, I could have done like this:

class Foo {
    public constructor() {
        this.welcome = this.welcome.bind(this)
    }

    public welcome(msg: string): void {
        console.log(`hello ${msg}`)
    }
}

function bar(msg: string, fn: void): void {
    fn(msg)
}

const foo = new Foo()
bar('world', foo.welcome) // 'hello world'

In addition, I should point out that I was inspired by this clear explanation .

Hope it helps !

You could use fat arrow functions. They shouldn't lose "this"

class Foo {
    public somefunc = () => {
        // do some
    }
    public anyfunc = () => {
        // do any
    }
}

Javascript would allow this, but not sure if that is what you want?

class Foo {
 public someFunc(name:string){
  return "Hello, " + name;
 }

function bar(funcName: string) {
    return eval(funcName);
}

console.log(bar("new Foo().someFunc('erik')"));

In my opinion, you should use facade design pattern for this case.

When you create a complex library, tool or system,consisting of many functions and/or classes; It gets hard to understand and dependent. So you should implement a class which provides a simple uniform interface.

class Foo{

      public somefunc() {
        console.log("some")
      }

      public anyfunc() {
        console.log("any")
      }


    };


    class FuncFacade {

      getFunc(obj: any, func_name: string) {
        switch (func_name) {
          case obj.somefunc.name: {
            return obj.somefunc;
          }

          case obj.anyfunc.name: {
            return obj.anyfunc;
          }
          default: {
            throw new Error("No such func!");
          }

        }
      }
    }

    let ff = new FuncFacade();

    function bar(obj: Foo, func_name: string) {
      ff.getFunc(obj,func_name)();
    }

    bar(new Foo(), Foo.prototype.anyfunc.name);

Maybe this was not what you asked for but this is the way how it should be.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM