microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
99.68k stars 12.34k forks source link

Allow pass through of class-properties #12212

Open jthomaschewski opened 7 years ago

jthomaschewski commented 7 years ago

TypeScript Version:

Description I expect Typescript to more or less only strip types but preserve the rest - based on the target compilerOption. Especially when using a target like es2017 or even esnext.

Class-properties are always moved into the constructor. This prevents hot-reloading of class-property functions when using react-hot-loader 3.

Using them is a common pattern for binding event-handler functions to this without having to do this for every method in the constructor.

Code

class MyClass {
  prop1: number = 123;
  handleClick = () => {
    console.log('Click handled');
  }
  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

Expected emit:

class MyClass {
  prop1 = 123;
  handleClick = () => {
    console.log('Click handled');
  }
  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

Actual emit:

class MyClass {
  constructor() {
    super(...arguments);
    this.prop1 = 123;
    this.handleClick = () => {
      console.log('Click handled');
    };
  }
  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}
mhegazy commented 7 years ago

We need to move the property initialization transformation to the esnext transform.

mhegazy commented 7 years ago

PRs are welcomed.

mheiber commented 5 years ago

we're working on this: https://github.com/bloomberg/TypeScript/pull/10/files

joeywatts commented 5 years ago

I've observed a difference in the ESNext proposal spec for class properties and the current TypeScript class behavior. Consider this code:

class A {
    property = 'some string';
}
class B extends A {
    property;
}
const instanceB = new B();

It seems that, with the wording of the ES proposal, property gets redefined in B, so that instanceB.property === void 0. The babel implementation seems to call Object.defineProperty in the case where a modification must be made to an existing property descriptor.

It's certainly possible to make TypeScript do the same thing, but this change has significant risk of breaking existing code. This change would likely be observed by many users since TypeScript property declarations are not only used for runtime effect - but also to add type information. (Therefore, it's likely that many are actually using property declarations with no initializer.) See the following example:

class Greeting {
    greeting: string = 'generic greeting';
    greet() { console.log(greeting); }
}
class HelloGreeting extends Greeting {
    greeting: 'hello'; // Narrows type from string to "hello"
}
const greet = new HelloGreeting();
greet.greet();
// Current TS behavior: prints "generic greeting"
// ES proposal behavior: prints undefined

If TS is to stay consistent with ES, then a breaking version change will need to be made (with a flag to opt-in to the existing behavior?)

RyanCavanaugh commented 5 years ago

@joeywatts thanks for raising this -- logged #27644