镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Property initializers output for non-initialized types #45076

Description

Bug Report

This is technically not a bug, given ES Spec, however, I'm filing an issue in case an consideration needs to be made on how to handle with regard to documentation, etc, and/or to provide a solution for people facing the same.

Issue Summary

  • ESNext spec seems to call for defining all class fields. This also affects those which are optional or specified with a boom.
  • Spec also dictates that property initializers are called after the super call. (https://github.com/tc39/proposal-class-fields)

This results in any properties specified in the inherited class that are assigned during the base class constructor to be overwritten, even if they do not have initializer values specified in the inherited class.

The behaviour which causes the error can be seen below:

class A {
  a?: string
}

Output is:

// target = ESNext
class A {
  a; // Outputs a statement without initialized value, which is treated as a = undefined;
}

// target = ES2020
class A {
}

Here is a simplified version of how this affected me

abstract class Base<T extends Record<string, any>> {
  // Single constructor for all derivatives
  constructor(o: Omit<T, typeof Base>) {
    Object.assign(this, o);
  }
}

class A extends Base<A> {
  myProp?: string
  myProp2!: string
}

const a = new A({ myProp2: 'hello' });

// ESNext: a = { myProp2: undefined, myProp: undefined }
// ES2020: a = { myProp2: 'hello' }

The above produces:

// TypeScript target=ESNext
class A extends Base {
  myProp;
  myProp2; // Note the boomed property still gets output here, so it is initialized after the super call - which maybe surprising behaviour
}

// ES2020 / Babel
class A extends Base {
}

🔎 Search Terms

  • property initializers

🕗 Version & Regression Information

TS 4.3.5

Solution

For those facing this issue, simply change property declarations to ambient.

ie:

class A extends Base<A> {
  declare myProp?: string
  declare myProp2: string
}

Activity

  1. jcalz commented on Jul 18, 2021

    @jcalz
    Contributor

    Relevant: the --useDefineForClassFields compiler flag is what controls this behavior, and this behavior is documented since TypeScript 3.7. From TypeScript 3.7 until TypeScript 4.2, this compiler flag was disabled by default in all circumstances.

    It seems that starting in TypeScript 4.3, this compiler option is now enabled by default if your target is ESNext. (See #42663 for implementation). This is likely what you are running into here. But, according to this comment in #34787, this change is not documented anywhere in the release notes for TypeScript 4.3.

    So possibly this bug report is a request that the TypeScript 4.3 release notes should mention this as a breaking change? (And maybe it belongs on the TypeScript-website issue list instead of here?)

  2. nonara commented on Jul 18, 2021

    @nonara
    Author

    It seems that starting in TypeScript 4.3, this compiler option is now enabled by default if your target is ESNext. (See #42663 for implementation).

    Ah! That's the culprit. Thanks.

    So possibly this bug report is a request that the TypeScript 4.3 release notes should mention this as a breaking change?

    That seems reasonable. I see several people commented that they ran into it as well, and it took some time to track down. It sidelined a good chunk of my day, also, when I bumped TS versions. Was a little tricky to figure out what was happening.

    Adding to the documentation is probably a good idea. Looks like the issue was flagged as breaking change also, so it probably was intended to be in the notes

  3. MartinJohns commented on Jul 18, 2021

    @MartinJohns
    Contributor

    It seems that starting in TypeScript 4.3, this compiler option is now enabled by default if your target is ESNext. (See #42663 for implementation). This is likely what you are running into here. But, according to this comment in #34787, this change is not documented anywhere in the release notes for TypeScript 4.3.

    To be fair, "ESNext" is not really a stable target and is breaking by definition. :-)

  4. fatcerberus commented on Jul 18, 2021

    @fatcerberus

    For what it’s worth: useDefineForClassFields makes sense to enable by default when targeting ESNext, since otherwise behavior for each property may differ depending on whether a property has an initializer or not (esnext implies no transpilation and native class fields use [[Define]] semantics).

    The change definitely should be documented, though.

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

    DocsThe issue relates to how you learn TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions