Babel: Class Property Initializers

Created on 28 Jan 2015  ·  32Comments  ·  Source: babel/babel

Source: http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#es7-property-initializers

I haven't seen any spec proposal, so I'm suggesting property initializers to be a playground feature.

// Future Version
export class Counter extends React.Component {
  static propTypes = { initialCount: React.PropTypes.number };
  static defaultProps = { initialCount: 0 };
  state = { count: this.props.initialCount };
  tick() {
    this.setState({ count: this.state.count + 1 });
  }
  render() {
    return (
      <div onClick={this.tick.bind(this)}>
        Clicks: {this.state.count}
      </div>
    );
  }
}

equivalent to:

export class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
  }
  tick() {
    this.setState({count: this.state.count + 1});
  }
  render() {
    return (
      <div onClick={this.tick.bind(this)}>
        Clicks: {this.state.count}
      </div>
    );
  }
}
Counter.propTypes = { initialCount: React.PropTypes.number };
Counter.defaultProps = { initialCount: 0 };
outdated

Most helpful comment

Does not work for me:

class A extends Component{
    someFunc=()=>{}
}
> ERROR in ./src/file.js
Module build failed: SyntaxError: /file/path/file.js: 'super.*' is not allowed before super() (This is an error on an internal node. Probably an internal error)

But:

class A{
    someFunc=()=>{}
}

Builds fine.

I figured maybe the plugin was adding the fn.bind(this) in the constructor without adding super(), so I figured if I do this, it should work, and for a time, it did:

class A extends Component{
    constructor(props,context){
        super(props,context);
    }
    handleKeyDown=(evt)=>{
    }
}

...But then this did not:

class A extends Component{
    constructor(props,context){
        super(props,context);
    }
    handleKeyDown=(evt)=>{
        const {dispatch,mode} = this.props;
    }
}

So I'm not sure at all what is happening.


//.babelrc:
{
  "presets": [
    "es2015",
    "stage-0",
    "react"
  ]
}
//package.json dependencies
{
    "babel-cli": "^6.1.2",
    "babel-core": "^6.1.2",
    "babel-loader": "^6.0.1",
    "babel-preset-es2015": "^6.1.2",
    "babel-preset-react": "^6.1.2",
    "babel-preset-stage-0": "^6.1.2"
}
> node --version
v5.0.0
> npm --version
3.3.12

Not sure where to post, might be related to https://github.com/babel/babel/issues/2694 or https://github.com/babel/babel/issues/2305.

If, as I suppose, this is a bug, and not me being retarded, is there any way to turn off this check, as I have dozens upon dozens of React components that are all relying on this syntax to autobind events handlers?

All 32 comments

:+1: This has been one of my "why didn't they include that?!" niggles with classes.

Also, autobinding:

class Counter extends React.Component {
  tick = () => {
    ...
  }
  ...
}

See facebook/react#2972. I'm going to wait for more information before I implement anything as the technical details of how this would work are very important. Adding it preemptively while knowing so little will only cause issues down the road.

+1, dear adult people please spec this

Only @sebmarkbage has the authority to spec this since he knows the technical behaviour.

+1

Can't see anything related in https://github.com/tc39/ecma262 going to ask on ESDiscuss

@benjamingr There's no proposal, nothing exists beyond the initial concept presented in the React blog post.

@sebmck thanks, I've asked on esdiscuss if there are any plans for this. Here https://esdiscuss.org/topic/es7-property-initializers

Also wanted to reference traceur's discussions since they actually already have support for both instance and static member initializers.

https://github.com/google/traceur-compiler/issues/1415
https://github.com/google/traceur-compiler/pull/1593
https://github.com/google/traceur-compiler/commit/6522ad9cd8f194355f5da5d7314ccd07341d9f69

REPL Demo

This is a little outdated, but its a gist describing the concepts for a proposal that I'm planning to put on the agenda for the March meeting:

https://gist.github.com/jeffmo/054df782c05639da2adb

A couple of the high-level things that need updating are: (a) There is no @@create anymore, so property initialization likely needs to happen during super() (b) Initializer execution needs to be called with the this context of the object being allocated and property [HomeObject] (c) The gist makes the case against having to necessarily call super() in child-class constructors -- but it's now been settled in ES6 that this is now required in order to have access to this in the constructor body. So that argument is now moot.

In any case, it's only [a very early version of] my proposal and it hasn't gone through the committee or any rigorous review yet -- so I wouldn't act much on it right now beyond just providing feedback.

I really like how traceur has handled this, as shown by Jay's post:

$traceurRuntime.ModuleStore.getAnonymousModule(function(require) {
  "use strict";
  var Person = function Person() {
    this.firstName = 'Bilbo';
    this.lastName = 'Baggins';
    this.constructor.count++;
  };
  ($traceurRuntime.createClass)(Person, {}, {count: 0});
  var dudebro = new Person();
  Person.count;
  return {};
});

With properties added to the prototype you can do something like this and it resolves correctly:

class Foo {
    one = 1;
    two = this.one + 1;
}

The only issue I see is that static properties do not resolve this correctly, which is a bit tricky if asking the question: What does this refer to in the context of a static property?

  • If it's the extended class, you would have to initialize the property again when the class is extended, requiring you to tuck the property away in a closure to execute later.
  • If it's class that it's defined in, easiest to do, but may have some surprises, like expecting the former behavior.

Possibly the easiest thing to do is make this illegal to use in the context of a property. Most languages with class properties don't allow you to use dynamic expressions let alone refer to the instance. I'm not saying disallow dynamic expressions (like foo = {}), but perhaps binding this in the property context should be left for a future extension.

@lukescott Funny, I was just bringing that up here https://gist.github.com/jeffmo/054df782c05639da2adb#comment-1391104

Note that Java allows this references inside instance member initializer expressions and it does indeed reference the instance. But it does not allow static member initializers to use it: ERROR: non-static variable this cannot be referenced from a static context static public String result = this.kickDog();

You can, however, reference static class members with their more qualified name: result = Person.kickDog(); (this is perhaps expected)

@jeffmo Happy to go with that behaviour if you and @sebmarkbage are fine with it. I just didn't want to roll my own and infer the behaviour as I don't have authority on this and it would've just lead to compat issues in the future.

@sebmck Perhaps you could simplify the implementation until something is drafted. Like disallow this all together incase that's what they decide to do in the spec. Would be easier to add a feature than take it away.

I would get immediate use out of it even if I was restricted to strings, numbers, and booleans.

@sebmck : Yea I totally get it. We haven't rolled this into any formal transpilers of our own yet either for the same reason.

I'll likely hack together a very small proof-of-concept transpiler to play with things and track updates to the proposal as they come along -- but it seems like a good thing to keep that separate from anything "official" looking until we have more head nods from more people that this is a good direction for the spec.

@jeffmo Yeah, I'll shove this into the playground which is basically my bucket to do whatever. So far I've been keeping the "experimental" label for ES7 stage >= 0 proposals found here.

Should also just note that if I add this to the playground then I'm going to do breaking changes related to this in minor versions as I don't want it to interfere with mainstream development too much.

Will be available in the next minor under the playground flag:

class MyClass {
  myProp = { someValue: 42 };
  static myProp = { someValue: 42 };
}

assert.equal(MyClass.myProp.someValue, 42);

var myClass = new MyClass;
assert.ok(myClass.myProp !== MyClass.prototype.myProp);
assert.equal(myClass.myProp.someValue, 42);

NOTE: It does not address the "Inheritance Hazards" and "Scoping Hazards" part of @jeffmo's gist. I've also taken the liberty of adding static property initializers.

Available as of 3.6.0 behind the playground flag, use at your own risk.

@sebmck

Thank you!

It would be nice to support autobinding use case from the same blog post:

class Counter extends React.Component {
  tick = () => {
    ...
  }
  ...
}

With current implementation, this is undefined. (I don't really care about static properties, just about normal ones. Autobinding some methods is a common use case for React components.)

@gaearon I think the goal right now is to keep it simple until something more authoritative comes along. There are a lot of questions about what this means in this context, especially with inheritance. Also, is there any reason why tick can't be defined as a method?

@sebmck Awesome work! Risk I shall! Although I'm only using it for simple defaults, so I should be fairly safe.

@gaearon I'm actually pretty sure this being the allocated instance makes sense even with inheritance. A base class will never have access to properties initialized by a subclass in this step - since those must be executed after the super returns - which makes sense.

I also think that all property initializers should execute, even if they're overridden by a subclass which is what is already natural in the old-style. This makes it easy to also access super properties.

this is sick :(

class A {
  x = 5;
}

class B extends A {
  x = 0;
}

let b = new B();

console.log(b.x); // 5

@shuvalov-anton Can you open a new issue please? It's hard to keep track of everything.

@sebmck done, #847

A complete implementation will be available in the next major behind the experimental flag. It's not recommended for production use as breaking changes may be introduced without bumping majors in order to track the proposal as it develops.

For anyone coming across this, I wanted to share my workaround using just ES6 features. I use static getters (one could use static setters too):

class Context extends React.Component {
  static get childContextTypes() {
    return {
      app: React.PropTypes.instanceOf(App).isRequired
    }
  }

  getChildContext() {
    return { app: this.props.app }
  }

  render() {
    return this.props.children[0]
  }
}

Static getters are inherited by subclasses. The effect of using a getter is almost the same as using a static initializer - I think that the only difference is that in the case of a getter, the getter function is invoked every time the property is accessed.

If you wanted a static property that can be updated, you would have to use a private variable, and reference it in your getter and setter. For example:

class MyClass {

  static get myProp() {
    return this._myProp || { value: 'default' }
  }

  static set myProp(val) {
    this._myProp = val
  }

}

So are fat arrows supposed to be working? I'm using babel core 6.0.20 and babel-preset-es2015 6.0.15, and I get errors like this if I try to use fat arrows:

Module build failed: SyntaxError: /Users/jemminger/Development/react/babel/client/components/counter/index.js: Unexpected token (11:10)
   9 |   }
  10 | 
> 11 |   onClick = (e) => {
     |           ^
  12 |     this.setState({ value: this.state.value++ });
  13 |   }

Am I doing it wrong?

@jemminger Property initializers aren't part of ES2015.

^ Yeah, if you are trying to use class properties it's not part of the es2015 preset. You need http://babeljs.io/docs/plugins/transform-class-properties/
or http://babeljs.io/docs/plugins/preset-stage-1/.

For questions and support please visit the Slack community or StackOverflow.

The Babel issue tracker is exclusively for bug reports and feature requests.

Does not work for me:

class A extends Component{
    someFunc=()=>{}
}
> ERROR in ./src/file.js
Module build failed: SyntaxError: /file/path/file.js: 'super.*' is not allowed before super() (This is an error on an internal node. Probably an internal error)

But:

class A{
    someFunc=()=>{}
}

Builds fine.

I figured maybe the plugin was adding the fn.bind(this) in the constructor without adding super(), so I figured if I do this, it should work, and for a time, it did:

class A extends Component{
    constructor(props,context){
        super(props,context);
    }
    handleKeyDown=(evt)=>{
    }
}

...But then this did not:

class A extends Component{
    constructor(props,context){
        super(props,context);
    }
    handleKeyDown=(evt)=>{
        const {dispatch,mode} = this.props;
    }
}

So I'm not sure at all what is happening.


//.babelrc:
{
  "presets": [
    "es2015",
    "stage-0",
    "react"
  ]
}
//package.json dependencies
{
    "babel-cli": "^6.1.2",
    "babel-core": "^6.1.2",
    "babel-loader": "^6.0.1",
    "babel-preset-es2015": "^6.1.2",
    "babel-preset-react": "^6.1.2",
    "babel-preset-stage-0": "^6.1.2"
}
> node --version
v5.0.0
> npm --version
3.3.12

Not sure where to post, might be related to https://github.com/babel/babel/issues/2694 or https://github.com/babel/babel/issues/2305.

If, as I suppose, this is a bug, and not me being retarded, is there any way to turn off this check, as I have dozens upon dozens of React components that are all relying on this syntax to autobind events handlers?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wdalrymple picture wdalrymple  ·  43Comments

loganfsmyth picture loganfsmyth  ·  73Comments

amereii picture amereii  ·  44Comments

rayj10 picture rayj10  ·  61Comments

armpogart picture armpogart  ·  34Comments