-
-
Save andrewzey/9a836eaee90d4092c485fbb0668a46b7 to your computer and use it in GitHub Desktop.
import React, {Component} from 'react'; | |
import createReactClass from 'create-react-class'; | |
import PropTypes from 'prop-types'; | |
// Problem: When writing React components, ES6 Classes present two annoyances: | |
// 1. No lexical pre-binding of `this` in class method references. | |
// 1a. Means we have to manually bind, but if we do that in render, we're | |
// creating lots of extraneous anonymous functions every render | |
// 1b. Alternately, we would have to meticulously "pre-bind" all of our | |
// class methods in the constructor (see example `MyES6Component`) | |
// 2. Awkwardness with lack of class static properties (esp. propTypes and defaultProps) | |
// Using CreateClass: | |
// ***************************************** | |
// ***************************************** | |
const MyReactComponent = createReactClass({ | |
propTypes: { | |
doSomething: PropTypes.func, | |
}, | |
// The fact that this is a function, rather than being `defaultProps` object like `propTypes` is a peculiar API choice | |
getDefaultProps() { | |
return { | |
doSomething: () => {}, | |
}; | |
}, | |
handleClick(event) { | |
this.props.doSomething(event); | |
}, | |
handleKeyUp(event) { | |
this.props.doSomething(event); | |
}, | |
componentDidMount() { | |
// note that createReactClass lexically pre-binds this.handleKeyUp for us. Handy :) | |
window.addEventListener('keyup', this.handleKeyUp); | |
}, | |
componentWillUnmount() { | |
// note that React lexically pre-binds this.handleKeyUp for us, and this is an | |
// identical function reference as when we registered the listener. Handy :) | |
window.removeEventListener('keyup', this.handleKeyUp); | |
}, | |
render() { | |
// note that React lexically pre-binds this.handleKeyUp for us. Handy :) | |
return <div onClick={this.handleClick}></div>; | |
}, | |
}); | |
// Using ES6 Class | |
// ***************************************** | |
// ***************************************** | |
class MyES6Component extends Component { | |
constructor(props) { | |
// Boilerplate | |
super(props); | |
// Yuck, we have to do this for all class methods?! | |
// I suppose we could make a new base class or HOC that does this, but then | |
// we have lots of API surface area to support / reimplement. Gross. | |
this.handleClick = this.handleClick.bind(this); | |
this.handleKeyUp = this.handleKeyUp.bind(this); | |
} | |
handleClick(event) { | |
this.props.doSomething(event); | |
} | |
handleKeyUp(event) { | |
this.props.doSomething(event); | |
} | |
componentDidMount() { | |
// Note, we could NOT have done the binding here, as then addEventListener | |
// and removeEventListener would have different function references (each | |
// their own anonymous function) | |
window.addEventListener('keyup', this.handleKeyUp); | |
} | |
componentWillUnmount() { | |
// Note, we could NOT have done the binding here, as then addEventListener | |
// and removeEventListener would have different function references (each | |
// their own anonymous function) | |
window.removeEventListener('keyup', this.handleKeyUp); | |
} | |
render() { | |
// Note that we could have defined the arrow function at call time like this: | |
// return <div onClick={event => this.handleClick(event)}></div>; | |
// but that means we're creating a new anonymous function on every render!! | |
// so instead we use the prebound version just like in createClass | |
return <div onClick={this.handleClick}></div>; | |
} | |
} | |
// eww, why is this out here?! | |
// ES6 should have included class properties (statics)! | |
MyES6Component.propTypes = { | |
doSomething: PropTypes.func, | |
}; | |
// eww, why is this out here?! | |
// ES6 should have included class properties (statics)! | |
MyES6Component.defaultProps = { | |
doSomething: () => {}, | |
}; | |
// Using ES6 Class with ES7 Class Properties (Static and Instance) | |
// ***************************************** | |
// ***************************************** | |
class MyES7Component extends Component { | |
// no longer floating outside the class definition. hooray! | |
static propTypes = { | |
doSomething: PropTypes.func, | |
} | |
// no longer floating outside the class definition. hooray! | |
static defaultProps = { | |
doSomething: () => {}, | |
} | |
// instance property that's bound. hooray! | |
handleClick = event => { | |
this.props.doSomething(event); | |
} | |
// instance property that's bound. hooray! | |
handleKeyUp = event => { | |
this.props.doSomething(event); | |
} | |
componentDidMount() { | |
window.addEventListener('keyup', this.handleKeyUp); | |
} | |
componentWillUnmount() { | |
window.removeEventListener('keyup', this.handleKeyUp); | |
} | |
render() { | |
return <div onClick={this.handleClick}></div>; | |
} | |
} |
On the flip side, class properties are still only Stage 2 (Draft).
So the "safe" thing to do is probably to just stick with createReactClass...
The ES7 version is likely future proof, as I doubt the syntax and semantics there will change, but it's possible, and that could make a horrible debugging experience should anything be wrong with Babel's implementation.
Basically it boils down to whether we want to refactor now, making our code more "standard JS", but running the risk of there being implementation issues with the Stage 2 proposal - OR - wait it out and continue to use createReactClass until the proposal is more mature and we can be sure we aren't jumping the gun in making debugging more painful.
Our eventual goal is for ES6 classes to replace React.createClass completely, but until we have a replacement for current mixin use cases and support for class property initializers in the language, we don't plan to deprecate React.createClass.
via: https://facebook.github.io/react/blog/2015/03/10/react-v0.13.html
Conclusion, enable: https://babeljs.io/docs/plugins/transform-class-properties/