iceblueorbitz
iceblueorbitz

Reputation: 1060

Typescript nullable callback

I'm trying to create a class which allows passing a callback to alter the side-effects of a method. If you don't pass a callback, then the method will be called directly. This is a basic example:

class Button<T = void> {
    private clickWrapper?: (click: Function) => T

    private _click() {
        // do the click here
        return null;
    }

    constructor(clickWrapper?: (click: Function) => T) {
        this.clickWrapper = clickWrapper;
    }

    public click() {
        if (this.clickWrapper) {
            return this.clickWrapper(this._click.bind(this));
        } else {
            return this._click();
        }
    }

}


class Foo {

    public doStuff() {
        console.log('hello');
    }

}



const button = new Button<Foo>(click => {
    // do some stuff
    click();
    return new Foo();
});

const foo = button.click();
foo.doStuff();


const button2 = new Button();
button2.click();

This works, but foo.doStuff() complains that foo may be null - even though in this case I provided a clickWrapper, so the return value of button.click() cannot be null, it must be an instance of Foo. Is there a better way to define this?

The second issue is I have to copy the Button constructor's parameter type when I've already declared it for Button.clickWrapper. How do I avoid having to declare the type on the private property and constructor parameter?

Upvotes: 0

Views: 744

Answers (2)

rafal2228
rafal2228

Reputation: 416

I have updated you code snippet:

class Button<T = null> {
  constructor(private clickWrapper?: (click: Function) => T) {}

  private _click() {
    // do the click here
    return null;
  }

  public click(): T {
    if (this.clickWrapper) {
      return this.clickWrapper(this._click.bind(this));
    } else {
      return this._click();
    }
  }
}

class Foo {
  public doStuff() {
    console.log("hello");
  }
}

const button = new Button<Foo>(click => {
  // do some stuff
  click();
  return new Foo();
});

const foo = button.click();
foo.doStuff();

const button2 = new Button();
button2.click();

Two things:

  • TypeScript can't be sure what is exact return type of your public click function so it assumes T | null, since default _click function returns null
  • To avoid redeclaring types for constructor and property of an object, you can always use shorthand syntax for constructor assignment (just add private or public keyword to constructor param)

Upvotes: 1

Alessandro Aguilar
Alessandro Aguilar

Reputation: 401

interface Callback<V> {
  (arg: () => void): V
}

class Button<T = void> {
  constructor(private callback?: Callback<T>) {}

  private onClick = () => {

  }

  public click = () => {
    if (this.callback) {
      return this.callback(this.onClick)
    } else {
      return this.onClick()
    }
  }
}

const button = new Button<number>(
  click => {
    click()
    return 2 +2 
  }
)

console.log(button.click()) // 4

I update your code to solve your problems

  • Create an interface for the callback type and add the private callback? to the constructor to inject the argument to the class
  • There are many types for a function, in typescript a function that not return nothing is a void function, you are returning null, so that didn't match with your clickWrapper type, I assume you aren't gonna return anything from the click function so I update that type to match too with a void function

Upvotes: 0

Related Questions