Home >Web Front-end >JS Tutorial >Optimizing React Performance with Stateless Components

Optimizing React Performance with Stateless Components

Lisa Kudrow
Lisa KudrowOriginal
2025-02-16 11:35:08262browse

Optimizing React Performance with Stateless Components

Optimizing React Performance with Stateless Components

This article explores stateless components in React. This type of component does not contain this.state = { ... } calls, and only processes incoming "props" and child components.

Summary of key points

  • The stateless component in React does not contain any this.state = { … } calls. They only handle incoming "props" and subcomponents, which makes them simpler and easier to analyze from a test perspective.
  • Stateless components can be converted into functional components, which is more like pure JavaScript and has less dependence on the framework used.
  • The performance of stateless components can be optimized by using React.PureComponent, which has a built-in shouldComponentUpdate method that allows for shallow comparisons of each prop.
  • Recompose is a utility library for function components and advanced components in React. It can be used to render function components without re-rendering when props are unchanged.
  • Stateless components can significantly improve the performance of React applications because they do not manage their own state or lifecycle methods, thus reducing code volume and memory consumption. However, they also have some limitations, such as not being able to use lifecycle methods or managing their own state.

Basics

<code class="language-javascript">import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}</code>

The code runs normally. It's very basic, but it builds examples.

It should be noted:

  • It is stateless, without this.state = { ... }.
  • console.log is used to understand its usage. Especially when performing performance optimization, if props are not actually changed, unnecessary re-rendering needs to be avoided.
  • The event handler here is "inline". This syntax is convenient because its code is close to the elements it processes, and this syntax means you don't need to do anything .bind(this).
  • Using such an inline function will have a little performance loss, because the function must be created every time it is rendered. This will be detailed later.

Display component

We now realize that the above component is not only stateless, it is actually what Dan Abramov calls a display component. It's just a name, but basically, it's lightweight, produces some HTML/DOM and doesn't handle any state data.

So we can make it into a function! Not only does this feel "cool", it also makes it less terrible because it's easier to understand. It receives input and, independently of the environment, always returns the same output. Of course, it "callback" because one of the props is a callable function.

Let's rewrite it:

<code class="language-javascript">import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}</code>

It feels good, right? It feels like pure JavaScript, you can write it without thinking about the framework you are using.

The problem of continuous re-rendering

Suppose our User component is used in a component that changes state over time. But the state does not affect our components. For example, like this:

<code class="language-javascript">const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
         userSelected()
       }}>
      {name}
    </h3>
  </div>
}</code>

Run this code and you will notice that even if nothing changes, our components will be re-rendered! This is not a big problem now, but in practical applications, components tend to become more and more complex, and every unnecessary re-rendering will cause the website to slow down.

If you use react-addons-perf to debug this application now, I'm sure you will find that time is wasted on rendering Users->User. Oops! what to do? !

It seems that everything is showing that we need to use shouldComponentUpdate to cover how React thinks props are different, and we are sure they are not different. In order to add a React lifecycle hook, the component needs to be a class. Alas. So we go back to the original class-based implementation and add a new lifecycle hook method:

Back to class component

<code class="language-javascript">import React, { Component } from 'react'

class Users extends Component {
  constructor(props) {
    super(props)
    this.state = {
      otherData: null,
      users: [{name: 'John Doe', highlighted: false}]
    }
  }

  async componentDidMount() {
    try {
      let response = await fetch('https://api.github.com')
      let data = await response.json()
      this.setState({otherData: data})
    } catch(err) {
      throw err
    }
  }

  toggleUserHighlight(user) {
    this.setState(prevState => ({
      users: prevState.users.map(u => {
        if (u.name === user.name) {
          u.highlighted = !u.highlighted
        }
        return u
      })
    }))
  }

  render() {
    return <div>
      <h1>Users</h1>
      {
        this.state.users.map(user => {
          return <user name="{user.name}" highlighted="{user.highlighted}" userselected="{()"> {
              this.toggleUserHighlight(user)
            }}/>
         })
      }
    </user>
</div>
  }
}</code>

Pay attention to the newly added shouldComponentUpdate method. This is a bit ugly. Not only can we no longer use functions, but we also have to manually list props that may be changed. This involves a bold assumption that the userSelected function prop will not change. This is unlikely, but it needs attention.

But please note that even if the included App component is re-rendered, this will only be rendered once! So, this is good for performance. But can we do better?

React.PureComponent

Since React 15.3, there is a new component base class. It's called PureComponent, and it has a built-in shouldComponentUpdate method that makes a "shallow comparison" of each prop. marvelous! If we use this we can discard our custom shouldComponentUpdate method which must list specific props.

<code class="language-javascript">import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}</code>

Try it and you will be disappointed. It re-renders every time. Why? ! The answer is because the userSelected function is recreated every time in the App's render method. This means that when a PureComponent-based component calls its own shouldComponentUpdate(), it returns true, because the function is always different, because it is created every time.

Usually, the solution to this problem is to bind the function in the constructor that contains the component. First, if we do this, it means we have to type the method name 5 times (and 1 before):

  • this.userSelected = this.userSelected.bind(this) (in the constructor)
  • userSelected() { ... } (as the method definition itself)
  • <user ... userselected="{this.userSelected}"></user> (where the rendering of the User component is defined)

Another problem is that, as you can see, when the userSelected method is actually executed, it depends on a closure. In particular, it relies on the scope variable this.state.users.map() from the iterator. user

Admittedly, there is a solution to this problem, which is to first bind the

method to userSelected and then when the method is called (in a child component), pass the this (or its name) go back. Here is a solution like this. user

recompose to rescue!

First, let's review our goals:

  1. Writing functional components feels better because they are functions. This immediately tells the code reader that it does not save any state. They are easy to reason from a unit test perspective. And they feel more concise and more like pure JavaScript (and of course JSX).
  2. We are too lazy to bind all methods passed to child components. Of course, if the methods are complex, it is better to refactor them instead of creating them dynamically. Dynamic creation methods mean we can write their code directly where they are used, we don't have to name them, nor have we mentioned them 5 times in three different places.
  3. Subcomponents should be re-rendered only when props change. This may not matter for small and fast components, but for practical applications, when you have many of these components, all of these extra renderings waste CPU when can be avoided.
(In fact, our ideal situation is that the component is rendered only once. Why can't React solve this problem for us? In that case, the "How to Make React Faster" blog posts will be reduced by 90%.)

According to the documentation, recompose is "a library of React utility tools for function components and advanced components. Think of it as a lodash for React."

. There are a lot to explore in this library, but now we want to render our function components without re-rendering when props have not changed. The first time we tried rewriting it back to the function component with recompose.pure, it looks like this:

<code class="language-javascript">import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}</code>

You may notice that if you run this code, the User component will still re-render even if the props (

and

keys) have not changed. name highlightedLet's go a step further. We don't use

, but we use

, which is a version of recompose.pure, but you can specify the prop key you want to focus on: recompose.onlyUpdateForKeys recompose.pure

<code class="language-javascript">const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
         userSelected()
       }}>
      {name}
    </h3>
  </div>
}</code>

After running this code, you will notice that it will only be updated when

or

props change. If the parent component is re-rendered, the User component does not. name highlightedLong live! We found gold!

Discussion

First, ask yourself if it's worth optimizing your component performance. Maybe that's more than it's worth it. Your components should be lightweight, maybe you can move any expensive calculations out of the components and move them into external memorable functions, or you can reorganize your components so that some data cannot be No waste of rendering components when used. For example, in this example, you may not want to render the User component before fetch is done.

Writing the code in the most convenient way for you, then starting your program and iterating from there to make it perform better is not a bad solution. In this example, in order to improve performance, you need to define the function component from:

<code class="language-javascript">import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
           userSelected()
         }}>
        {name}
      </h3>
    </div>
  }
}</code>

…changed to…

<code class="language-javascript">const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3 style="{{fontStyle:" highlighted : onclick="{event"> {
         userSelected()
       }}>
      {name}
    </h3>
  </div>
}</code>

Ideally, rather than showing a way to bypass the problem, the best solution is a new patch from React, which makes a huge improvement to shallowEqual to "automatically" identifying the incoming and comparing is a function, and just because it is not equal does not mean that it is actually different.

Agree! There is a compromise alternative to having to deal with inline functions that bind methods in the constructor and each recreation. That is the public class field. It is a phase 2 feature in Babel, so your setup will likely support it. For example, here is a branch that uses it, which is not only shorter, but now also means we don't need to list all non-function props manually. This solution must abandon closures. Nevertheless, it is still good to know and realize recompose.onlyUpdateForKeys when needed.

For more information about React, please check out our course "React The ES6 Way".

This article was peer-reviewed by Jack Franklin. Thanks to all SitePoint peer reviewers for making SitePoint’s content perfect!

Frequently Asked Questions about Optimizing React Performance with Stateless Components

What is the main difference between a stateful component and a stateless component in React?

State components (also known as class components) maintain memory about the changes in component state over time. They are responsible for how components behave and render. On the other hand, stateless components (also called functional components) do not have their own state. They receive data from the parent component in the form of props and render it. They are mainly responsible for the UI part of the component.

How to optimize the performance of my React application using stateless components?

Stateless components can significantly improve the performance of React applications. Since they do not manage their own state or lifecycle methods, they have less code, which makes them easier to understand and test. They can also reduce the amount of memory consumed by the application. To optimize performance, you can convert state components to stateless components as much as possible and use React's PureComponent to avoid unnecessary re-rendering.

What is PureComponent and how does it help optimize React performance?

PureComponent is a special React component that helps optimize the performance of your application. It uses shallow prop and state comparison to implement the shouldComponentUpdate lifecycle method. This means it will re-render only when the state or props change, which can significantly reduce unnecessary re-rendering and improve performance.

How to convert a state component in React to a stateless component?

Converting a state component to a stateless component includes removing any state or lifecycle method from the component. The component should only receive data through props and render it. Here is an example:

// Status component class Welcome extends React.Component { render() { return

Hello, {this.props.name}
; } }

// Stateless Component function Welcome(props) { return

Hello, {props.name}
; }

What are the best practices for using stateless components in React?

Some best practices for using stateless components in React include: use as many of them as possible to improve performance and readability; keep them small and focus on a single function; and avoid using state or lifecycle methods. It is also recommended to use props deconstruction to write cleaner code.

Can stateless components use lifecycle methods in React?

No, stateless components cannot use lifecycle methods in React. Lifecycle methods are only applicable to class components. However, with the introduction of Hooks in React 16.8, you can now add state and lifecycle features to function components.

What are the limitations of stateless components in React?

While stateless components have many advantages, they also have some limitations. They cannot use lifecycle methods or manage their own state. However, these limitations can be overcome by using React Hooks.

How can stateless components improve the readability of code?

Stateless components improve code readability through simplicity and clarity. They do not manage their own state or use lifecycle methods, which makes them simpler and easier to understand. They also encourage the use of small, reusable components, which can make the code more organized and easier to maintain.

Can stateless components handle events in React?

Yes, stateless components can handle events in React. Event handlers can be passed as props to stateless components. Here is an example:

function ActionLink(props) { Return ( Click me ); }

How does stateless components promote the modularization of code?

Stateless components promote modularization of code by promoting the use of small, reusable components. Each component can be developed and tested independently, which makes the code easier to maintain and understand. It also encourages separation of concerns, where each component is responsible for a single function.

The above is the detailed content of Optimizing React Performance with Stateless Components. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn