Lee bare,





navigation

  • Blog garden
  • Home page
  • New essays
  • contact
  • To subscribe to
  • management

statistical

  • Essay – 2
  • The article – 0
  • Comments – 2
  • References – 0

How to optimize React functional components

The article is the first personal blog

preface

purpose

This article covers only the performance tuning methods that are specific to functional components, not both class components and functional components, such as the use of keys. In addition, this article does not cover the use of the API in detail, which can be fixed by hooks.

For the reader

Having worked with React functional components and hooks, and at least seen documentation on useState, useCallback, and the useMemo API, this article will have a familiar feel if you’ve worked with class components for performance tuning.

React performance optimization

I think the React performance optimization concept has two main directions:

  1. Reduce the number of rerenders. Because the heaviest piece in React (and the one that takes the longest) is the Reconction (which simply translates to diff), which does not occur without render.

  2. Reduce the amount of computation. Mainly to reduce double computation, for functional components, each render will execute the function call from scratch again.

When using class components, the React optimization API is mainly used: ShouldComponentUpdate and PureComponent, both apis are designed to reduce the number of times the parent component is updated and the child component is updated. While it is possible to prevent the current component from rendering when state is updated, to do so, prove that your property is not suitable for state, but should be static or placed outside the class as a simple variable.

But with no cycles declared and no classes in functional components, how do you optimize performance?

React.memo

The react. Memo API is the PureComponent of the react. memo class, which reduces the need for rerender.

Examples of possible performance problems

For example, 🌰, let’s start with two pieces of code:

In the root directory, there is an index.js, and the code is as follows. The implementation is basically: a title on the top, a button in the middle (click button to change the title), and a puppet component below, passing in a name.

// index.js import React, { useState } from "react"; import ReactDOM from "react-dom"; import Child from './child' function App() { const [title, Return (<div className="App"> <h1>{title}</h1> <button onClick={() => SetTitle (" title has been changed ")} name > change < / button > < Child name = "peach peach" > < / Child > < / div >). } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);Copy the code

There is a child-.js in the sibling directory

// child.js
import React from "react";

function Child(props) {
  console.log(props.name)
  return <h1>{props.name}</h1>
}

export default ChildCopy the code

When first rendered it looks like this:

And the console prints “peaches” to prove that the Child component is rendered.

Next click on the change name button and the page will become:

The title has changed, and the console has printed “peach”, so you can see that although we changed the state of the parent component, the parent component has been re-rendered, and the child component has been re-rendered. You might be thinking that the props passed to the Child component have not changed, and it would be nice if the Child component had not been re-rendered. Why would that be?

We assume that the Child component is a very large component, and rendering at one time will consume a lot of performance, so we should minimize rendering of this component, otherwise it will easily cause performance problems. Therefore, if the Child component is not changed, even if the parent component is re-rendered, the Child component should not be rendered.

So how do we make sure that the sub components don’t render while the props aren’t changing?

The answer is to use react. Memo to render the same results with the same props, and to improve the performance of the components by memorizing the results of component rendering.

The basic usage of React. Memo

React.memo is a higher-order function that passes a component in and returns a component that can be remembered.

Function Component(props) {/* Props render */} const MyComponent = react.memo (Component);Copy the code

The Child component of the above example could look like this:

import React from "react";

function Child(props) {
  console.log(props.name)
  return <h1>{props.name}</h1>
}

export default React.memo(Child)Copy the code

The react. memo wrapped component will not be re-rendered if the props remain the same. In the example above, only the title will change when I click on the name. However, the Child component does not re-render (the effect is that the log inside the Child is not printed on the console), and reuses the result of the last rendering.

This effect is very similar to the PureComponent effect in class components, except that the former is used for function components and the latter for class components.

React. Memo advanced usage

By default, this method compares complex objects (props) only in a superficial way. If you want to control the comparison, pass in a second parameter to your custom comparison function.

Function MyComponent(props) {/* use props */} function areEqual(prevProps, NextProps) {/* Return true if the return from passing nextProps to render is the same as the return from passing prevProps to render, Export default react. memo(MyComponent, areEqual);Copy the code

This is from the React website.

If you’ve used shouldComponentUpdate() in a class component, you’ll be familiar with the react. memo second argument, but note that areEqual returns true if props areEqual; If props are not equal, return false. This is the opposite of the return value of the shouldComponentUpdate method.

useCallback

Now change the requirement again based on the example above. Add a subtitle to the requirement above and a button that changes the subtitle. Then put the button that changes the title into the Child component.

The purpose of putting the button that changed the title into the Child component is to pass the event that changed the title to the Child component through props, and then observe that the event might cause a performance problem.

First look at the code:

The parent component index. Js

// index.js import React, { useState } from "react"; import ReactDOM from "react-dom"; import Child from "./child"; Function App() {const [title, setTitle] = useState(" this is a title"); Const [subtitle, setSubtitle] = useState(" I'm a subtitle "); Const callback = () => {setTitle(" title changed "); }; return ( <div className="App"> <h1>{title}</h1> <h2>{subtitle}</h2> <button onClick={() => </button> <Child onClick={callback} name=" /> </div>); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);Copy the code

Subcomponents child. Js

import React from "react"; function Child(props) { console.log(props); <> <button onClick={props. OnClick}> </button> <h1>{props. Name}</h1> </>); } export default React.memo(Child);Copy the code

First render effect

This code will display the image above when first rendered, and the console will print out peaches.

Then when I click on this button to change the subtitles, subtitle becomes “subtitle change”, and the console will print out the peach peach again, it is proved that the subcomponents again apply colours to a drawing, but the Child components don’t have any change, then the Child component to apply colours to a drawing is redundant, so how to avoid off the redundant rendering?

Looking for a reason

Before we solve the problem, we should first know what causes the problem?

When a component is re-rendered, there are three general situations:

  1. Either the state of the component itself changes

  2. Either the parent component rerenders, causing the child component to rerender, but the props of the parent component is not modified

  3. Either the parent component rerenders, causing the child component to rerender, but the props passed by the parent component changes

Then use the elimination method to find out what causes it:

The first one obviously excludes that clicking the change subtitle does not change the state of the Child component;

The parent component is rendering again. The props passed by the parent component to the child component are not changed, but the child component is rendering again. We used the React.

For example, when the parent component is rendering again, the props passed to the Child component are changed. For example, when the parent component is rendering again, the props passed to the Child component are changed. For example, when the parent component is rendering again, the props passed to the Child component are changed. Why does the callback passed to onClick change? As stated at the beginning of this article, each time a function component is re-rendered, the function component is re-executed from the beginning, so the callback function created on both times must have changed, causing the child component to re-render.

How to solve

The solution is to use the useCallback API to keep the references of the two functions the same when re-rendering without changing the function.

UseCallback Usage

const callback = () => {
  doSomething(a, b);
}

const memoizedCallback = useCallback(callback, [a, b])Copy the code

Passing the function and its dependencies as arguments to useCallback will return the Memoizedversion of the callback function, which will be updated only if the dependency changes.

Then you can change index.js to look like this:

// index.js import React, { useState, useCallback } from "react"; import ReactDOM from "react-dom"; import Child from "./child"; Function App() {const [title, setTitle] = useState(" this is a title"); Const [subtitle, setSubtitle] = useState(" I'm a subtitle "); Const callback = () => {setTitle(" title changed "); }; // Memory callback via useCallback, MemoizedCallback = useCallback(callback, []) return ( <div className="App"> <h1>{title}</h1> <h2>{subtitle}</h2> <button onClick={() => </button> <Child onClick={memoizedCallback} name=" memoizedCallback "/> </div> } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);Copy the code

This way we can see that only peaches will be printed on the first render, not when you click change subtitle and change title.

If our callback passes a parameter and needs to add a new cache when the parameter changes, we can place the parameter in the array of the second parameter of useCallback as a form of dependency, similar to useEffect.

useMemo

As mentioned at the beginning of this article, React has two main performance optimizations: one is to reduce the number of rerenders (or unnecessary rendering), and the other is to reduce the amount of computation.

The react. memo and useCallback introduced earlier are both intended to reduce the number of rerenders. To reduce the amount of computation, that’s what useMemo does, so let’s look at an example.

function App() { const [num, setNum] = useState(0); Function expensiveFn() {let result = 0; for (let i = 0; i < 10000; i++) { result += i; } console.log(result) // 49995000 return result; } const base = expensiveFn(); <div className="App"> <h1>count: {num}</h1> <button onClick={() => setNum(num + base)}>+1</button> </div>); }Copy the code

The first render looks like this:

This example is simple, click the +1 button, then add the current value (num) to the value after the expensiveFn call, then set the value to num and display it, on the console output 49995000.

Performance problems may occur

Even a seemingly simple component can cause performance problems, so take a look at this simple example to see what else is worth optimizing.

First we treat the expensiveFn function as a costly function (you can replace I with 10000000, for example), then every time we click the +1 button, we re-render the component, and we call the expensiveFn function and print 49995000. Because each call to expensiveFn returns the same value, we can find a way to cache the calculated value. Each call to expensiveFn returns the cached value directly, so that we can do some performance optimization.

UseMemo caches calculation results

Use useMemo to cache the value of the expensiveFn function.

First of all, the basic usage of useMemo is introduced. The detailed usage can be found on the official website:

Return XXX} const memoizedValue = useMemo(computeExpensiveValue, [a, b]);Copy the code

The first argument to useMemo is a function whose value is cached as useMemo’s return value. The second argument is an array dependency. If the value in the array changes, the function in the first argument is executed again. The value returned by the function is cached and used as the return value of useMemo.

Now that you know how to use useMemo, you can optimize the above example as follows:

function App() { const [num, setNum] = useState(0); function expensiveFn() { let result = 0; for (let i = 0; i < 10000; i++) { result += i; } console.log(result) return result; } const base = useMemo(expensiveFn, []); <div className="App"> <h1>count: {num}</h1> <button onClick={() => setNum(num + base)}>+1</button> </div>); }Copy the code

Execute the code above, and now you can observe that no matter how many times we click +1, 49995000 will only print once, which means expensiveFn only executed once and achieved what we want.

summary

The usage scenario of useMemo is mainly used to cache the results of functions with a large amount of calculation, which can avoid unnecessary double calculation. Students who have used VUE may feel that it has the same effect as the calculation attributes in VUE.

But two caveats

If no dependency array is provided, useMemo computs the new value every time it renders.

Second, if the computation is very small, you can also choose not to use useMemo, because this optimization will not be the main point of the performance bottleneck, but may cause some performance problems.

conclusion

Performance bottlenecks may be less encountered for small projects, because of the small amount of computation and uncomplicated business logic. However, performance bottlenecks may be encountered for large projects, but there are many aspects to performance optimization: Network, critical path rendering, packaging, images, caching, etc., need to be optimized by yourself. This article only introduces the tip of the iceberg in performance optimization: React optimization during operation.

  1. React: Reduce the number of render times; Reduce double counting.
  2. See the useCallback section to find out how React causes performance problems.
  3. If you think about it this way, if you have only one large component in the entire page, then when the props or state is changed, it is the entire component that needs to reconction. In fact, you just change a text. If you make a reasonable component separation, You can control more granular updates.

There are many other benefits to splitting components properly, such as better maintenance, and this is the first step in learning about componentalization. Splitting components properly is an art form, and if not, it can lead to state chaos and more coding and more thinking.

Recommend the article

I’ve only covered how to optimize functional components here. For more React optimization tips, read the following article:

  • 21 React Performance Optimization Tips
  • React Performance Optimization Direction

Afterword.

I am taoweng, a thinking front-end ER, want to know about more front-end related, please pay attention to my public number: “front-end Taoyuan”, if you want to join the exchange group to follow the public number of “wechat” to pull you into the group

This article is published by OpenWrite!

Lee bare,
The editor
collection


Refresh the comments
Refresh the page
Return to the top



Blog garden