2
class Foo {
  foo: () => void
}

class Bar extends Foo {
  foo() {}
}

Is there any way to tell TypeScript to allow the above example?

Playground

Class 'Foo' defines instance member property 'foo', but extended class 'Bar' defines it as instance member function.

aleclarson
  • 18,087
  • 14
  • 64
  • 91

2 Answers2

5

That's because you can call a parent method, but not a parent instance member.

The reason for that is proper methods are saved to the prototype chain, whereas member functions are not.

class Foo {
    foo() {
        return `Successfully called Foo.foo()`;
    }

    bar = () => {
        return `Successfully called Foo.bar()`;
    }
}

console.log(
    ('foo' in Foo.prototype), // true (methods are correctly inherited)
    ('bar' in Foo.prototype), // false (instance properties are not inherited)
);

If the property is not in the property chain, an attempt to call it by using super will cause a runtime error.

class Bar extends Foo {
    foo = () => {
        return super.foo(); // Good: `foo` is in the prototype chain
    }

    bar = () => {
        return super.bar(); // Runtime error: `bar` is not in the prototype chain
    }
}

This makes it safe to go from a method to class instance property (here: foo), but not the other way around.

Karol Majewski
  • 23,596
  • 8
  • 44
  • 53
2

The other way around is allowed by TypeScript.

class Foo {
  foo() { }
}

class Bar extends Foo {
  foo: () => void
  constructor() {
    super()
    this.foo = () => { }
  }
}

You could also prefix the method with an underscore and then bind it in the constructor.

class Foo {
  foo: () => void
}

class Bar extends Foo {
  constructor() {
    super()
    this.foo = this._foo.bind(this)
  }
  _foo() {}
}

It would be nice if TypeScript let you do the following:

class Foo {
  foo: () => void
}

class Bar extends Foo {
  constructor() {
    super()
    // TypeScript should see that the method is always bound.
    this.foo = this.foo.bind(this)
  }
  foo() {}
}
aleclarson
  • 18,087
  • 14
  • 64
  • 91