Alternate component forms
In React, there are three ways to define a component. The way we've seen so far uses ES2015 classes to define a component and its methods. This is currently the most common method for defining React components and, in fact, the one you'll encounter most often in documentation and in this book.
React.createClass
Before ES2015 and its class syntax became popular and brought into React, the way to define a component was by using the React.createClass
function. This function takes as an argument a JavaScript object that describes the component and its methods. This conceptually is very similar to the way we have seen so far, but has some syntactic differences. To demonstrate, let's take a look at what our NewsItem
component looks like using this method:
React.createClass({ propTypes: { titleText: PropTypes.string.isRequired }, getInitialState() { return { expanded: false } }, onClick() { this.setState({ expanded: !this.state.expanded }); }, renderBody() { if (this.state.expanded) return ( <div> <Byline /> <Description /> </div>; ); } return null; }, render() { return ( <div className="news-item" onClick={this.onClick} > <Image /> <Title highlighted > {this.props.titleText} </Title> {this.renderBody()} </div> ); } });
Other than the obvious syntactic differences, there are a few subtle differences in how we define and use components with React.createClass
that we should draw our attention to. The first is instead of simply assigning the state in the class constructor, we define a getInitialState
method in the component, which returns the initial component state as an object:
getInitialState() { return { expanded: false } }
The next thing we might notice is that, previously, event handler functions were bound to the component's this
context either in the constructor or within the event attribute assignment. When using the React.createClass
syntax, we have no longer need to explicitly bind the context:
<div className="news-item" onClick={this.onClick} >
We may have also noticed that rather than defining the propTypes
statically on the class, we instead do it within the component object:
propTypes: { titleText: PropTypes.string.isRequired }
This component does not need default properties, but if it did, we would also define those inside the component object. We do this by defining a method similar to getInitialState
called getDefaultProps
that also returns an object:
getDefaultProps() { return { someProp: 'some value' } };
Functional components
For simple components that maintain no internal state, we can define them simply as functions that take props as input and return JSX elements as output. These components are not only succinct, but may in the future be more performant than components defined in other ways. For these reasons, it is recommended that we use functional components wherever possible.
Because of its simplicity and lack of internal state, our Title
component from an earlier section is a good candidate for being a functional component. Here is what that component would look like with this alternate syntax:
const Title = (props) => ( <h1 style={{ backgroundColor: props.highlighted ? 'yellow' : 'white', fontSize: `${props.fontSize}px` }} > {props.children} </h1> );
Taking advantage of ES2015 arrow function syntax, our large traditionally defined component has been simplified to a single function.
In addition to not having internal state, functional components don't have lifecycle methods. They can, however, have defaultProps
and propTypes
that can be specified in the same manner as class components:
Title.propTypes = { titleText: PropTypes.string.isRequired };