As front-end programmers now know, React is componentized. When I started learning React, I remember there were many different ways to write components. Today, the React community is more mature, but there is no complete guide to how components should be written properly.

This article will discuss how to write high-quality React components from the author’s point of view.

Before we begin, the following points need to be addressed:

  • This article and code examples are written in ES6 or ES7.
  • Some basic concepts are no longer popularised. React for beginners;
  • If you have any questions, please leave a message.

Best practices for Class-based Components

Class-based components are stateful, with their own methods, lifecycle functions, in-component state, and so on. Best practices include, but are not limited to, the following:

1) Importing CSS

I love the idea of CSS in JavaScript. In React, the “dream” of introducing CSS files for each React component became a reality. In the following code example, I separate the introduction of CSS files from the other dependencies:

import React, {Component} from 'react'
import {observer} from 'mobx-react'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'Copy the code

Of course, this is not really CSS in JS, and there are many solutions in the community. I fork a multi-dimensional comparison of various CSS in JS solutions on Github, and those who are interested can click here.

2) Set the initial State

When writing a component, pay attention to setting the initial state. Using our knowledge of ES6 modularity, we ensure that this component is exposed in the form of “Export Default” to facilitate the invocation of other modules (components) and team collaboration.

import React, {Component} from 'react'
import {observer} from 'mobx-react'

import ExpandableForm from './ExpandableForm'
import './styles/ProfileContainer.css'

export default class ProfileContainer extends Component {
    state = { expanded: false}...Copy the code

3) Set propTypes and defaultProps

PropTypes and defaultProps are static properties of the component. In the component code, these two properties should be set as high as possible. This is because it makes it easy for other code readers or developers to review and see the information at a glance. This information, like component documentation, is important to understand or become familiar with the current component.

Also, in principle, you need to write components that have propTypes attributes. As follows:

export default class ProfileContainer extends Component {
    state = { expanded: false }

    static propTypes = {
        model: React.PropTypes.object.isRequired,
        title: React.PropTypes.string
    }

    static defaultProps = {
        model: {
            id: 0
        },
        title: 'Your Name'
    }Copy the code

Functional Components are pure Components with no state, no methods. We should write and use this type of component to the fullest extent possible. These components are functions whose parameters are props, and we can reasonably set the initial state and assign values.

function ExpandableForm({ onExpand, expanded = false, children, onSubmit }) {
    const formStyle = expanded ? {height: 'auto'} : {height: 0}
    return (
        <form style={formStyle} onSubmit={onSubmit}>
            {children}
        <button onClick={onExpand}>Expand</button>
        </form>
    )
}Copy the code

4) Component Methods

When writing component methods, especially if you are passing a method as props to a child component, you need to make sure this refers to it correctly. We usually use the bind or ES6 arrow functions for this purpose.

export default class ProfileContainer extends Component {
    state = { expanded: false} handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.changeName(e.target.value) } handleExpand = (e) => { e.preventDefault() this.setState({ expanded: ! this.state.expanded }) }Copy the code

Of course, this is not the only approach. There are many different ways to implement this. I have a special article to compare the React binding to this component. You can refer to it here.

5) setState accepts a Function as a parameter (Passing setState a Function)

In the above code example, we used:

this.setState({ expanded: ! this.state.expanded })Copy the code

Here, there is a very “interesting” question about the setState hook function. React uses the Batch concept to optimize performance. It collects a “wave” of state changes and processes them in a unified manner. Just like the browser drawing document implementation. So when you setState, maybe the state doesn’t change right away, it’s an asynchronous process.

This means that we need to be careful about using the current state in setState, because the current state may not be reliable. To get around this problem, we can do this:

this.setState(prevState => ({ expanded: ! prevState.expanded }))Copy the code

We pass a function to the setState method that takes last-moment state to ensure that setState executes immediately.

Eric Elliott’s React setState design also started a “fight” with setState() Gate. As a crowd of onlookers, while we are eating melon, we will certainly harvest a lot of ideas in daishen Daoism, suggest reading.

If you are still confused about the asynchrony of the setState method, you can discuss it with me. I won’t go into it here.

6) Rational use of Destructuring Props

There’s not much to say about this, but take a closer look at the code: we use deconstructed assignment. In addition, if a component has multiple props, each props should be on a separate line for better writing and reading.

export default class ProfileContainer extends Component {
    state = { expanded: false} handleSubmit = (e) => { e.preventDefault() this.props.model.save() } handleNameChange = (e) => { this.props.model.changeName(e.target.value) } handleExpand = (e) => { e.preventDefault() this.setState(prevState => ({ expanded: ! prevState.expanded })) }render() {
        const {model, title} = this.props

        return ( 
            <ExpandableForm 
            onSubmit={this.handleSubmit} 
            expanded={this.state.expanded} 
            onExpand={this.handleExpand}>
                <div>
                    <h1>{title}</h1>
                    <input
                    type="text"
                    value={model.name}
                    onChange={this.handleNameChange}
                    placeholder="Your Name"/>
                </div>
            </ExpandableForm>
        )
    }
}Copy the code

7) Use Decorators

This one is for developers using Mobx. If you don’t understand Mobx, take a quick look. We emphasize the use of ES Next decorate for our components, as follows:

@observer
export default class ProfileContainer extends Component {Copy the code

Using modifiers is more flexible and readable. Even if you don’t use modifiers, you need to expose your component like this:

class ProfileContainer extends Component {
    // Component code
}
export default observer(ProfileContainer)Copy the code

8) Closures

It is important to avoid the following:

<input
    type="text"
    value={model.name}
    // onChange={(e) => { model.name = e.target.value }}
    // ^ Not this. Use the below:
    onChange={this.handleChange}
    placeholder="Your Name"/>Copy the code

Don’t:

onChange = {(e) => { model.name = e.target.value }}Copy the code

But:

onChange = {this.handleChange}Copy the code

The reason is simple: each time the parent component render, a new function is created and passed to the input. If input is a React component, this will cause the component’s re-render rudely. Remember that Reconciliation is the most expensive part of React.

In addition, we recommend an approach that makes reading, debugging, and changing easier.

7) Conditionals in JSX

Those of you who have actually written React projects will know that there can be a lot of conditional checks in JSX to render different component shapes for different situations. It looks like this:

Returned to the case

The result is not ideal. We lose the readability of our code, and the organization of our code is confusing. Multiple levels of nesting should also be avoided.

There are a number of libraries to address issues such as jSX-Control Statements, but rather than introduce third-party library dependencies, we should try to solve the problem ourselves.

At this point, do you miss if… The else? We can use curly braces to include the immediate-execute function IIFE to achieve if… Else purpose:

solution

Of course, there is a performance penalty for using a lot of instant-execution functions. So it’s worth considering the readability trade-offs. I would prefer to decompose this component because the logic of this component is already too complex and bloated. How to break it down? Please look at my article.

conclusion

In fact, every team has its own “React best practices”. Where is there a common “best practices”? The methods indicated in this article may not be suitable for all readers. It is essential to have a team of “best practices” for different code styles and development habits. On the other hand, the React stack itself is flexible and powerful.

In addition, this article is not my original work, but the translation of Our Best Practices for Writing React Components, and has been greatly expanded on this basis.

If you’re interested in the React ecosystem, here are some of my other articles:

  • React component design and decomposition thinking
  • Uber mobile web version is not enough to create the ultimate performance to see the real chapter
  • Analyze the Twitter front-end architecture to learn about complex scenario data design
  • React + ES next = ♥
  • React+Redux creates “NEWS EARLY”, a one-page app that understands the true nature of the cutting-edge technology stack
  • React + Redux project example
  • .

Happy Coding!

PS: Author Github warehouse and Zhihu Q&A link welcome all forms of communication.