Skip to content

Subclass method is not allowed (TS2425) if the parent class has a property of type any with the same name #27965

Open

Description

TypeScript appears to treat class methods as something fundamentally different from a class prototype property of type function. This is not in agreement with the standard semantics of ES6.

TypeScript Version: all versions, as far as I have been able to test.

Search Terms: method member property any parent class subclass

Code

class Super {
    prop: any;
}

class Derived extends Super {
    prop() {}
}

Expected behavior: should compile without a problem. Note that by the ES6 standard, the above definition of Derived is equivalent to the following, which TypeScript compiles without a problem:

class Derived extends Super {}

Derived.prototype.prop = function(){};

Actual behavior:

Error TS2425: Class 'Super' defines instance member property 'prop', but extended class 'Derived' defines it as instance member function.

Playground Link: https://www.typescriptlang.org/play/#src=class%20Super%20%7B%0D%0A%20%20%20%20prop%3A%20any%3B%0D%0A%7D%0D%0A%0D%0Aclass%20Derived%20extends%20Super%20%7B%0D%0A%20%20%20%20prop()%20%7B%7D%0D%0A%7D%0D%0A

Related Issues: I didn't find similar issues, although I started by looking for issues requesting for the ability to disable particular errors (TS2425 in this case). There are several (closed) issues in which such an ability is requested or suggested, but I believe the proper solution would be to bring TypeScript in agreement with ES6. After all, it claims to be a superset!

Activity

  1. ajafff commented on Oct 18, 2018

    @ajafff
    Contributor

    The problem here is that prop(){} defined as method is placed on the prototype. prop: any as property is written directly to the instance (if it had an initializer) and thus overrides the prototype method.
    Consider the following slightly modified version of your code:

    class Super {
        prop: any = undefined;
    }
    
    class Derived extends Super {
        prop() {}
    }
    
    new Derived().prop(); // runtime error: undefined is not a function

    You can still use a function to initialize prop in Derived:

    class Super {
        prop: any = undefined;
    }
    
    class Derived extends Super {
        prop = () => {}
    }
    
    new Derived().prop(); // works as expected
  2. jgonggrijp commented on Oct 18, 2018

    @jgonggrijp
    Author

    Thanks Klaus Meinhardt (@ajafff), I didn't think of that. The reason I'm running into this is not resolved yet, though. A common pattern in the JavaScript world is to allow an object property to either be of type A or a bound method of type () => A. Underscore's and Lodash's _.result is specifically meant to cater to this scenario. However, there seems to be no way in TypeScript to express such a typing:

    // first attempt
    
    class Super {
        prop: any;
    }
    
    class Derived extends Super {
        prop() {}
    }
    
    // problem as above
    // second attempt
    
    class Super {
        prop() { return {}; };
    }
    
    class Derived extends Super {
        prop = {};
    }
    
    // Property 'prop' in type 'Derived' is not assignable to the same property in base type 'Super'.
    //    Type '{}' is not assingable to type '() => {}'.
    //      Type '{}' provides no match for the signature '(): {}'.

    There may be a way I'm not aware of (I hope there is), but otherwise, I'd argue TypeScript is missing something.

  3. RyanCavanaugh commented on Oct 18, 2018

    @RyanCavanaugh
    Member

    What is Super doing here? Do you mean to have an interface instead?

  4. jgonggrijp commented on Oct 18, 2018

    @jgonggrijp
    Author

    My specific situation is as follows:

    // a library not originally written in TypeScript
    
    class Super {
        prop() {  // meant to be overriden either with {} or () => {}
            return {};
        }
    }
    // somebody else's @types/library/index.d.ts
    
    declare class Super {
        prop(): {};  // doesn't cover all cases
        prop: any;   // doesn't cover all cases either
    }
    // my code
    
    class Derived extends Super {
        // If I want to override prop in a way not supported by the typings, I have a problem.
    }

    But it also applies if the library were written in TypeScript in the first place.

  5. RyanCavanaugh commented on Oct 18, 2018

    @RyanCavanaugh
    Member

    After all, it claims to be a superset!

    This does not mean "all syntactically-legal ES6 code must not have type errors"; see https://stackoverflow.com/questions/41750390/what-does-all-legal-javascript-is-legal-typescript-mean

    This has come up in the past but the specific scenario of "this method may be overriden by a property of a different type" hasn't come up often enough to warrant a solution

  6. added
    SuggestionAn idea for TypeScript
    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this feature
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on Oct 18, 2018
  7. jgonggrijp commented on Oct 19, 2018

    @jgonggrijp
    Author

    This does not mean "all syntactically-legal ES6 code must not have type errors"; see https://stackoverflow.com/questions/41750390/what-does-all-legal-javascript-is-legal-typescript-mean

    Fair point. I started this ticket with different assumptions, so the opening post and the title probably don't accurately reflect anymore what this issue is really about.

    I'm not meaning to imply that TypeScript should just accept the example code I presented. Rather, the problem is that there seems to be no alternative. Semantically, it is a valid and even meaningful construction in JavaScript to override a base class method with a subclass non-function prototype property or vice versa. This is especially the case if the return type of the method is the same as the type of the non-function. To me, this seems like a scenario that could be type-checked, but again, there seems no way to do this in TypeScript.

    If it hasn't come up often, then apparently not many Backbone users work with TypeScript. In Backbone, _.result is used all over the place. You may be able to gain some ground there!

  8. pchodak commented on Jul 31, 2019

    @pchodak

    Got the same when using :

    class Super{
      test = function (){}
    }
    
    class Delivered extends Super {
      test() {}
    }
    
  9. avonwyss commented on Apr 24, 2020

    @avonwyss

    For completeness (and people seeking help with TS2425 errors), you can always use type merging to "tell" TS that the member shall be seen as method.

    The examples above for instance can be solved like this:

    interface Super {
        test?(): void;
    }
    
    class Super{
      test = function (){} // test may also be undefined, it's optional on the interface
    }
    
    class Delivered extends Super {
      test() {}
    }
  10. trusktr commented on May 8, 2020

    @trusktr
    Contributor

    Unfortunately, that doesn't work well:

    interface Super {
        test?: boolean | (() => boolean) // ERROR, All declarations of 'test' must have identical modifiers. (2687)
    }
    
    class Super {
      // I want this to be protected!
      protected test // ERROR, All declarations of 'test' must have identical modifiers. (2687)
    }
    
    class Delivered extends Super { // ERROR, Property 'test' is protected in type 'Delivered' but public in type 'Super'. (2415)
      protected test = true 
    }

    playground

    This is a pattern I've seen in base classes before (in plain JS).

    For example, here is a more representative example of what a base class may want to do (note the "ERROR 2425"):

    import * as React from 'react' // this is only for JSX types, but we're not using React in the following example...
    
    type Template = Element | (() => Element)
    
    interface Super {
    	template?: Template
    }
    
    class Super extends HTMLElement {
    	// Subclasses can optionally provide a `template`, writing JSX to create a DOM tree.
    	// BUT! Not all elements need to have their own Shadow DOM tree, so they don't have to define this.
    	protected template
    
    	connectedCallback() {
    		const tmpl = this.template
    		if (tmpl) this.render(typeof tmpl === 'function' ? tmpl : () => tmpl, this)
    	}
    
    	private render(template: () => Element, ctx: this) {
    		this.attachShadow({ mode: 'open' })
    		this.shadowRoot!.append(template.call(this))
    	}
    }
    
    
    // This element has a simple static Shadow DOM, written with concise syntax.
    class SomeElement extends Super {
    	protected template = div(<div>This should work.</div>)
    }
    
    customElements.define('some-element', SomeElement)
    
    // This element needs no inner Shadow DOM.
    class OtherElement extends Super {
    	connectedCallback() {
    		super.connectedCallback()
    		// ...does something special, without needing a `template`...
    	}
    }
    
    customElements.define('other-element', OtherElement)
    
    
    // This element make a local reactive variable (state) to render in its DOM. 
    class AnotherElement extends Super {
    	count = reactiveVar(0)
    
    	protected template() { // <------- ERROR 2425 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    		setInterval(() => this.count.set(this.count.get() + 1), 1000)
    
    		return div(<div>This should work too. The count is {this.count.get()}</div>) // updates the DOM every second.
    	}
    }
    
    customElements.define('another-element', AnotherElement)
    
    declare function reactiveVar<T>(initialValue: T): { get(): T; set(val: T): T }
    
    // This is simple a no-op identity function, to type cast JSX.Element to a DOM type.
    // See issues like
    // https://github.com/microsoft/TypeScript/issues/14729
    // https://github.com/microsoft/TypeScript/issues/31606
    // https://github.com/microsoft/TypeScript/issues/35929
    declare function div(j: JSX.Element): HTMLDivElement

    Now strip all the type from that example, and you'll see that it is valid and well-functioning JavaScript.

  11. trusktr commented on May 8, 2020

    @trusktr
    Contributor

    Some people mention that we can just use an arrow function, like

    class Derived extends Super {
        prop = () => {}
    }

    but this is not ideal because

    • it is wasteful to create one new function per instance, instead of sharing a single prototype function.
    • it does not work with inheritance (we can not use super in an arrow function to call the same function of a super class).

    Ryan Cavanaugh (@RyanCavanaugh) It seems that a super class having an initializer is the main foot gun that the current restriction aims to prevent.

    Can we perhaps have a way to allow overriding properties with prototype methods under the condition that the super class must not have an initializer for the property being overridden (and with strict types therefore requires it to be an optional property)?

    For example, what if

    class Super {
        prop?: boolean | (() => boolean)
    }
    
    class Derived extends Super {
        prop = true // ok
    }
    
    class Derived2 extends Super {
        prop() { return true } // ok
    }

    is valid with no type error, and

    class Super {
        prop?: boolean | (() => boolean) = false // This has an initializer. It could also be in the constructor.
    }
    
    class Derived extends Super {
        prop = true // ok
    }
    
    class Derived2 extends Super {
        prop() { return true } // error
    }

    has a type error that says something like "The Super base class property prop has an initializer, therefore the subclass Derived2 can not override prop with a prototype method."

    I think allowing this under certain conditions would be great!

  12. githoniel commented on Jun 28, 2020

    @githoniel

    I have an example here

    type Constructor<T> = new (...args: any[]) => T
    
    interface Mergeable {
      merge: (
        obj: any,
        omit: string[]
      ) => void;
    }
    
    function Orderable<T extends Constructor<Mergeable>>(Class: T) {
      return class OrderableClass extends Class {
        merge(obj, omit: string[] = []) { // Error here 
          return super.merge(obj, [...omit, 'before', 'after'])
        }
      }
    }

    Error message is

    Class 'Mergeable' defines instance member property 'merge', 
    but extended class 'OrderableClass' defines it as instance 
    member function.(TS2425)
    

    I know the difference between class prototype function and class property. We could fix the error by

    interface Mergeable {
      merge(
        obj: any,
        omit: string[]
      ): void
    }
    

    But in as in #18654 says they should behave different in strict mode.

    A method and a function property of the same type behave differently. 
    Methods are always bivariant in their argument, while function properties 
    are contravariant in their argument under strictFunctionTypes.
    

    How can I do this if I want to use function properties typechecking here with extend class override here?

  13. s-cork commented on Feb 2, 2021

    @s-cork

    I came across this error when I wanted subclasses to be able to signal that the method does not exist by setting it to null.

    What I want to be able to do

    interface A {
      // if a subclass wants they can set this method to null
      foo: null | (() => number); // property foo was also declared here
    }
    
    class A {
      foo(): number { return 1; } // duplicate property
    }
    
    class B extends A {}
    
    B.prototype.foo = null;

    I can however change the definition for class A to:

    class A {}
    A.prototype.foo = () => 1;
    // no problem - just as the first comment in this issue found

    But since this is valid typescript I would have assumed the first version was also valid...

    I can see how it's not. I guess it's because prototype is itself an instance of type A and so assigning foo to be a function is like defining an instance member property. Typescript considers it different to defining an instance member function inside the class body, even though it all ends up on the prototype anyway.

    It would be nice if my original code worked, I don't mind the workaround but I don't love it.

    Note the opposite is also problematic

    interface A {
      foo: null | (() => number);
    }
    
    class A {}
    A.prototype.foo = null;
    
    class B extends A {
        foo(): number { return 1; } 
        // class A defines an instance member property but extended class B defines it as an instance member function
    }
    // B.prototype.foo = () => 1 // no problems

    (I also want to avoid defining functions that end up on the instance and would prefer to keep them on the prototype).

  14. jgonggrijp commented on Feb 2, 2021

    @jgonggrijp
    Author

    I don't know who came up with this first, but TS and ESnext basically share the same critical mistake to consider a "class field"

    class A {
        prop = 'abc';
    }

    equivalent to a constructor initializer

    function A() {
        this.prop = 'abc';
    }

    instead of a default property on the prototype.

    function A() {}
    A.prototype.prop = 'abc';

    Besides the fact that this is inconsistent with member functions (which are set on the prototype), this causes all kinds of nasty problems. For example, you can't override a parent "class field" by setting a derived class prototype property.

    class Base {
        prop = 'abc';
    }
    
    class Derived {}
    Derived.prototype.prop = 'def';
    
    const d = new Derived();
    d.prop; // 'abc'

    If "class fields" were prototype properties instead, there wouldn't be any problem of this kind (below, I'm avoiding class field syntax in order to avoid confusion).

    class Base {}
    Base.prototype.prop = 'abc';
    
    class Derived1 {}
    Derived1.prototype.prop = 'def';
    
    class Derived2() {
        constructor() {
            this.prop = 'ghi';
        }
    }
    
    (new Derived1()).prop; // 'def'
    (new Derived2()).prop; // 'ghi'

    I think that the current issue ticket is a symptom of this problem being reflected in the TS type system.

    At first I thought that adding type checking to JS was a neat idea, but every time I ran into a problem like this (i.e., the TS type system being too incomplete to accurately reflect what's going on in JS), I became more convinced that a dynamically typed language simply isn't meant to have static type checking. Either you create a language that is actually statically typed and you can do all the type checking you want (cf. C++), or you create a dynamically typed language, enjoy the added flexibility and accept that you can only do runtime checks. Not to mention the lack of accurate typings for third-party libraries. Type checking isn't going very well in Python, either.

    I'm switching back to CoffeeScript, which has sane "class fields". This is an important reason for me to switch back, perhaps even the most important.

  15. pqnet commented on Mar 18, 2022

    @pqnet

    I don't know who came up with this first, but TS and ESnext basically share the same critical mistake to consider a "class field"

    class A {
        prop = 'abc';
    }

    equivalent to a constructor initializer

    function A() {
        this.prop = 'abc';
    }

    instead of a default property on the prototype.

    function A() {}
    A.prototype.prop = 'abc';

    I would argue that if you need a value to be the shared between every instance you should not allow yourself to use it with the same syntax that you'd access a member variable, because it may get confusing.
    You can use the specifier static if you want to add a member to the constructor itself.

    Besides the fact that this is inconsistent with member functions (which are set on the prototype), this causes all kinds of nasty problems. For example, you can't override a parent "class field" by setting a derived class prototype property.

    class Base {
        prop = 'abc';
    }
    
    class Derived {}
    Derived.prototype.prop = 'def';
    
    const d = new Derived();
    d.prop; // 'abc'

    Member functions are what prototypes are for, so that you can have a member invocation syntax on your function while implicitly passing the this argument. There is a clear difference in syntax in the way you declare a member and a property in Typescript/ES class, so a different behavior may be expected. If you want a function as a property you have the prop = function() { } syntax that you can use

    If "class fields" were prototype properties instead, there wouldn't be any problem of this kind (below, I'm avoiding class field syntax in order to avoid confusion).

    class Base {}
    Base.prototype.prop = 'abc';
    
    class Derived1 {}
    Derived1.prototype.prop = 'def';
    
    class Derived2() {
        constructor() {
            this.prop = 'ghi';
        }
    }
    
    (new Derived1()).prop; // 'def'
    (new Derived2()).prop; // 'ghi'

    I think that the current issue ticket is a symptom of this problem being reflected in the TS type system.

    At first I thought that adding type checking to JS was a neat idea, but every time I ran into a problem like this (i.e., the TS type system being too incomplete to accurately reflect what's going on in JS), I became more convinced that a dynamically typed language simply isn't meant to have static type checking. Either you create a language that is actually statically typed and you can do all the type checking you want (cf. C++), or you create a dynamically typed language, enjoy the added flexibility and accept that you can only do runtime checks. Not to mention the lack of accurate typings for third-party libraries. Type checking isn't going very well in Python, either.

    This rant of yours is a bit unfair. TS type system is one of the most expressive (and complex) type system around, and can express most of the shenanigans that people have done so far with Javascript, because it has been designed with the intention to retroactively add typings to existing libraries. Even this particular case is an example of Typescript semantic actually expressing correctly what happens with ES classes (to be fair ES classes were inspired and modelled after Typescript classes so there is some blame in Typescript if they don't appeal to your liking).
    As for "there is no use in do statical type check in a dynamic language" I'd argue that there is instead, and it's static analysis which can help reducing bugs: although Javascript is a dynamic language I'd argue that 99% of Javascript code should not use dynamic features and should be statically typable, in order to be "safer".
    As for those awesome Javascript tricks that you can do since it is actually a dynamic language, they shouldn't cover more than 1% of all the Javascript code. And typescript provides you with escape hatches from the "safe" static types like the any type, so that you can write that 1% of magic dynamic code, add a warning for reviewers that they need to pay special attention to that part, then go back to the safety and happiness of a statically typed language.

    I'm switching back to CoffeeScript, which has sane "class fields". This is an important reason for me to switch back, perhaps even the most important.

    If adding non-function properties to a prototype is something that happens often in your ordinary workload it is a good idea to use a language that allows for that to be expressed easily. I have no experience with CoffeeScript, so I can't advise for or against it.

  16. jgonggrijp commented on Mar 21, 2022

    @jgonggrijp
    Author

    Hey pqnet, thank you for taking the time to respond to my post.

    I would argue that if you need a value to be the shared between every instance you should not allow yourself to use it with the same syntax that you'd access a member variable, because it may get confusing. You can use the specifier static if you want to add a member to the constructor itself.

    I might agree with this way of thinking for a static language like C++ or C#, but not for a dynamic language like JavaScript or Python. In dynamic languages, there is no danger in "confusion" because properties on the prototype (or "attributes" on the class in Python) are intended as a transparent fallback value. Alternatively, you could say that programmers are encouraged to "confuse" between instance and prototype properties; the language feature was introduced specifically to facilitate this.

    The very idea is that you do not need to distinguish between an instance property and a prototype property. Reading the property on the instance, you do not need to be aware of the difference. Writing the property on the instance, you do not need to worry, because the property on the prototype will never be affected.

    Member functions are what prototypes are for, so that you can have a member invocation syntax on your function while implicitly passing the this argument.

    You write this as if meaning to imply that non-function members are not "what prototypes are for". I beg to differ; I think the creators of JavaScript added the option to define non-function prototype properties because they wanted them to be used. Indeed, they are almost universally used in JavaScript projects, and I think the use is valid in a large majority of cases.

    It seems that the aim of the TS project is to instill an opinion on what language features of JS should and shouldn't be used. Is it an official standpoint that prototypes should have only function members? In any case, while I see the added value of type checking, I never asked for a language that changes the semantics of JavaScript.

    There is a clear difference in syntax in the way you declare a member and a property in Typescript/ES class, so a different behavior may be expected.

    If you mean that the assignment notation for class fields is consistent with its semantics, then I somewhat agree. In C++ (and I guess C# and Java as well), the same notation is used to set a default value for an instance property, too:

    class Example {
        int property = 0;
    };

    However, these languages have no equivalent of prototype properties, so there is no other plausible meaning for the notation, unlike in JavaScript.

    In JavaScript, if class fields had the semantic of setting prototype properties, perhaps we would expect a colon notation instead, analogous to object literal notation:

    class Example {
        property: 0;
    }

    However, that notation is obviously unavailable for TypeScript, as it conflicts with type annotation syntax.

    What really gripes me, is that a notation was invented for something that already had a convenient notation:

    class Example {
        constructor() {
            this.property = 0;
        }
    }

    Thus leaving no obvious candidate for something that did not have a convenient notation yet:

    class Example {}
    
    Example.prototype.property = 0;

    Contrast this with CoffeeScript, which has both colon notation and assignment notation inside class bodies, using both notations in consistent ways that add real value. Colon notation is used both for function and non-function prototype properties:

    class Example
        property: 0
        method: -> return this.property

    If you want to set an instance property in the constructor, you can, just like in JavaScript:

    class Example
        constructor: ->
            this.property = 0

    Assignment notation at class body scope is used for a purpose that does not exist in languages like C++, i.e., to store variables that can be used in logic that runs at class definition during runtime (in C++ you can run logic during class definition, too, but it happens during compile time and it requires different notation). Here is a slightly advanced example of how that may be used for metaprogramming purposes:

    import { encoding } from './config.coffee'
    
    class Example
        utf8method = -> ### imagine lots of code ###
        utf16method = -> ### lots of different code ###
        method: if encoding is 'utf8' then utf8method else utf16method

    At first I thought that adding type checking to JS was a neat idea, but every time I ran into a problem like this (i.e., the TS type system being too incomplete to accurately reflect what's going on in JS), I became more convinced that a dynamically typed language simply isn't meant to have static type checking. Either you create a language that is actually statically typed and you can do all the type checking you want (cf. C++), or you create a dynamically typed language, enjoy the added flexibility and accept that you can only do runtime checks. Not to mention the lack of accurate typings for third-party libraries. Type checking isn't going very well in Python, either.

    This rant of yours is a bit unfair.

    I think I'm just stating a tradeoff. Surely you agree there is a tradeoff between static and dynamic typing? I just think that TypeScript is making a compromise, which ends up not working very well.

    TS type system is one of the most expressive (and complex) type system around,

    I will agree with that,

    and can express most of the shenanigans that people have done so far with Javascript,

    but I would like to nuance this. I think that 90% of normal JavaScript can be straightforwardly typed using TS annotations. Another 9% can be typed, but requires complex annotations or advanced techniques, which tend to be highly intimidating for beginners. The remaining 1% cannot be typed at all. Given the size of modern JavaScript projects, we end up hitting the undesirable 10% all the time.

    because it has been designed with the intention to retroactively add typings to existing libraries.

    This is how TS sometimes advertises itself, but it is not the whole story. From the way maintainers and proponents (including you) defend the ways in which TS departs from regular JS semantics, it is clear that another intention is to enforce a particular opinion about how JS should be used. Remarks like "that something is allowed does not mean you should do it" and your "prototypes are meant for member functions". The opinion of the TS compiler, and it proponents, appears to be that JavaScript code should be written like C# code, even though C# is static and class-based while JavaScript is dynamic and prototype-based.

    Even this particular case is an example of Typescript semantic actually expressing correctly what happens with ES classes (to be fair ES classes were inspired and modelled after Typescript classes so there is some blame in Typescript if they don't appeal to your liking).

    Exactly. I think it is madness that ES went along with it.

    As for "there is no use in do statical type check in a dynamic language"

    Note that this is not what I wrote. I think there is a use in principle, but it's simply not possible to do it in a way that is comprehensive, correct and convenient at the same time.

    I'd argue that there is instead, and it's static analysis which can help reducing bugs: although Javascript is a dynamic language I'd argue that 99% of Javascript code should not use dynamic features and should be statically typable, in order to be "safer". As for those awesome Javascript tricks that you can do since it is actually a dynamic language, they shouldn't cover more than 1% of all the Javascript code.

    Here you are making claims about what should and should not be done in JavaScript, confirming what I wrote above.

    Dynamic typing makes the programming language more powerful. Expressive power makes programmers more productive. I'm in favour of consistency and I'm open to discussions about situations in which dynamic construct are better avoided, but I don't think a compiler for a dynamic programming language should be ruling out entire domains of dynamic semantics. The syntax of the language itself (i.e., ES) even less so!

    And typescript provides you with escape hatches from the "safe" static types like the any type, so that you can write that 1% of magic dynamic code, add a warning for reviewers that they need to pay special attention to that part, then go back to the safety and happiness of a statically typed language.

    I agree the escape hatch is there, but you need it a lot in order to avoid all the problems with TS. Even static types are often problematic.

    If adding non-function properties to a prototype is something that happens often in your ordinary workload it is a good idea to use a language that allows for that to be expressed easily.

    Right.

  17. ackava commented on Jul 21, 2022

    @ackava

    Ryan Cavanaugh (@RyanCavanaugh)

    Interface implementation is public, mostly we would want protected members, take a look at following.

       class BaseClass {
    
            protected init: (... args: any[]) => any;
    
            constructor() {
                 // child classes can initialize members
                 // before executing further statements...
                 this.init?.(... arguments);
            }
       }
    
    class DerivedInstanceProperty {
    	protected init = () => {
    		this.a = "1";
    		this.b = "2";
    	}
    }
    
    class DerivedMethod {
            // @ts-ignore
    	protected init() {
    		this.a = "1";
    		this.b = "2";
    	}
    }

    This code ensures that init does not popup on intellisense.

    Performance

    https://jsfiddle.net/qkcatm81/

    Instance Member is 60% slower compared to Prototype Method. So argument of using instance member goes against performance.

    Current workaround for using // @ts-ignore is an extra step, which might hide an important error by hiding wrongly typed parameteres or any other error.

  18. RyanCavanaugh commented on Jul 28, 2022

    @RyanCavanaugh
    Member

    Akash Kava (@ackava) it seems like you're looking for

          declare protected init: (... args: any[]) => any;
  19. ackava commented on Aug 16, 2022

    @ackava

    Dirty Workaround

    class Super {
        prop() {
        }
    }
    
    // this will remove `prop` from existence
    // howerver derived class can still override it
    // without typescript warning
    delete Super.prototype.prop;
    
    class Derived extends Super {
        prop() {}
    }
  20. frostius commented on May 3, 2024

    @frostius

    In case this is useful for anyone else, this work-around gives me error free "ts-checking" with vanilla javascript.
    The core need is to allow a derived class to optionally define a function.
    I use this pattern frequently as part of an opt-in event/callback system pushing the details of event registration/de-registration into the base class.

    class Motor extends Events {
      onSunrise(a) {
        turnOnMotor();
      }
    } 
    
    class Events {
       constructor() {
         if( this.onSunrise ) addSunriseListener(this.onSunrise.bind(this));
       }
      dispose() {
         if( this.onSunrise ) delSunriseListener();
      }
    }
    
    /** Define to receive a callback
     * @function
     * @param {Date} time time of sunrise
     */
     Events.prototype.onSunrise=undefined;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions