Use React has been three years, in these three years inside also deposited a lot of best practices on React code optimization, today first write a part out and share with you to share. We'll see if the article is popular and then we'll see if we share the later ones.
For each best practice in this post I will provide two examples, one good and one bad, for comparison, and a preview of the .gif
image.
The article in this piece focuses on optimizing these three situations:
- Parent component update causes child component to render
- Wrong way of writing Props leads to component rendering
- Context updates lead to component rendering
After reading the article if you think it has helped you, please help to click a praise, your praise is the biggest motivation for my creation. comment kudos can get the source code!!!
Parent component update causes child component to render
Class Example
❎ Error Example Preview
❎ Error Example
import React, { Component } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
const { count } = this.state;
return (
<div className="parent">
<h5>Error Example</h5>
<p>Parent ComponentCount--{count}</p>
<button onClick={this.handleClick}>Add</button>
<Son />
</div>
);
}
}
class Son extends Component {
constructor(props) {
super(props);
}
render() {
console.log("Sub-component re-rendered!!!");
return <div className="son">Sub-components</div>;
}
}
export { Parent, Son };
In this example, a change in the state of the parent component causes the child component to be re-rendered, which is a very normal way to write code, but seriously, it will still cause a waste of performance, after all, the child component is re-rendered! Next, let's see how to solve this problem!
Note: This example does not mean to eliminate the need to write such code, in fact, optimization is also dependent on the scenario!
✅ Correct example 1
import React, { Component, PureComponent } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
const { count } = this.state;
return (
<div className="parent">
<h5>Correct example 1</h5>
<p>Parent ComponentCount--{count}</p>
<button onClick={this.handleClick}>Add</button>
<Son />
</div>
);
}
}
class Son extends PureComponent {
constructor(props) {
super(props);
}
render() {
console.log("Sub-component re-rendered!!!");
return <div className="son">Sub-components</div>;
}
}
export default Parent;
In this example we are mainly borrowing from PureComponent to inherit this class, and React will automatically perform shouldComponentUpdate for us to perform a shallow comparison optimization update of Props.
Note: Actually, in all seriousness, components in React are executed by React.createElement(Son), and the resulting component's Props reference is new every time, thus triggering a re-render!
✅ Correct example 2
import React, { Component } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
const { count } = this.state;
const { children } = this.props;
return (
<div className="parent">
<h5>Correct example 2</h5>
<p>Parent Component Count--{count}</p>
<button onClick={this.handleClick}>Add</button>
{children}
</div>
);
}
}
export default Parent;
<Parent>
<Son />
</Parent>
In the optimization of this example, we separate stateful and stateless components and use children to pass stateless components in. This will avoid pointless re-rendering! So why would writing it this way avoid re-rendering? Because using
children directly in the stateful component will avoid using React.createElement(Son) to render the child component in the stateful component! This can also be done to optimize!
✅ Correct example 3
import React, { Component, memo } from "react";
import { Son } from "./Bad";
const MemoSon = memo(() => <Son></Son>);
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
const { count } = this.state;
return (
<div className="parent">
<h5>Correct example 3</h5>
<p>Parent Component Count--{count}</p>
<button onClick={this.handleClick}>Add</button>
<MemoSon />
</div>
);
}
}
export default Parent;
In this example, the idea of optimization is similar to the one mentioned in example 1, we borrowed the memo function, which is actually an optimization tool for the Function component We are also cheeky here to force the use of a little! The idea of avoiding re-rendering is actually to compare references to Props as well. Decide whether to render or not !!!
✅ Correct example 4
import React, { Component, useState, Fragment } from "react";
import { Son } from "./Bad";
const ClickCount = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<Fragment>
<div>
<h5>Correct example 4</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
</div>
</Fragment>
);
};
class Parent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="parent">
<ClickCount />
<Son />
</div>
);
}
}
export default Parent;
In this example, our optimization is mainly to remove the state component into one component, so that the state change is separated from the child component. It also avoids the re-rendering of the child components!
Description: This optimization means seriously speaking or used quite little, depending on the situation use it!
Hooks Example
Error Example Preview
❎ Error Example
import { useState } from "react";
const Son = () => {
console.log("Sub-component re-rendered!!!");
return <div className="son">Sub-components</div>;
};
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<div className="parent">
<h5>Error Example</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
<Son />
</div>
);
};
export { Son, Parent };
For Hooks the above is also a very normal way of writing, but compared to Class components, Function components have the feature that each time the component is re-rendered, the function is re-executed once. For a Class component, it will only execute new Class once, which is actually quite scary when you think about it. For function components, each execution means a new context, a new variable, and a new scope. So we need to pay more attention to the performance optimization of function components.
✅ Correct example 1
import { useState } from "react";
const Parent = ({ children }) => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<div className="parent">
<h5>Correct example 1</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
{children}
</div>
);
};
export default Parent;
<Parent>
<Son />
</Parent
In this example, we use children to render child components directly, the principle of which has been explained in the Class component example above.
Description: Seriously speaking, combining the characteristics of function components this means of optimization is actually a cure for the symptoms, not the root cause!
✅ Correct example 2
import { useState, useMemo } from "react";
import { Son } from "./Bad";
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<div className="parent">
<h5>Correct example 2</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
{useMemo(
() => (
<Son />
),
[]
)}
</div>
);
};
export default Parent;
n this example we use the optimization Hook useMemo , we cache the Son component and only when the dependency changes we re-execute the function to complete the re-rendering, otherwise the timing is the same memoized, which helps avoid high overhead calculations at each rendering. It also avoids having to redeclare variables, functions, scopes, etc. in the child component each time.
Note: I think this optimization is absolutely brilliant because useMemo saves the component reference and does not re-execute the function component, thus avoiding the declaration of variables, functions, and scopes within the component. Thus, performance is optimized. Nice!
✅ Correct example 3
import { useState, memo } from "react";
import { Son } from "./Bad";
const SonMemo = memo(Son);
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<div className="parent">
<h5>Correct example 3</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
<SonMemo />
</div>
);
};
export default Parent;
In this example we use the api memo, mainly to compare whether the props reference has changed, thus avoiding the re-rendering of child components!
Wrong way of writing Props leads to component rendering
Class Example
❎ Error Example Preview
❎ Error Example
import React, { Component, PureComponent } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
const { count } = this.state;
return (
<div className="parent">
<h5>Error Example</h5>
<p>Parent Component Count--{count}</p>
<button onClick={this.handleClick}>Add</button>
<Son componentDetails={{ name: "Sub-components" }} anyMethod={() => {}} />
</div>
);
}
}
class Son extends PureComponent {
constructor(props) {
super(props);
}
render() {
const { componentDetails, anyMethod } = this.props;
console.log("Son -> render -> anyMethod", anyMethod);
console.log("Son -> render -> componentDetails", componentDetails);
return <div className="son">{componentDetails?.name}</div>;
}
}
export { Parent, Son };
The passing of Props in this example is directly wrong way to write it. Because the rendering of the component is mainly rendered by listening to the change of Props and State, that in this example passed props each time is a new object,*because the reference is different, each time the rendering of the parent component will lead to the rendering of the child component. * So the re-rendering of real numbers caused by this writing should not!
So how should we write it?
✅ Correct example 1
import React, { Component, PureComponent } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
componentDetails: { name: "Sub-components" },
};
}
handleClick = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
anyMethod = () => {};
render() {
const { count, componentDetails } = this.state;
return (
<div className="parent">
<h5>Correct example 1</h5>
<p>Parent Component Count--{count}</p>
<button onClick={this.handleClick}>增加</button>
<Son componentDetails={componentDetails} anyMethod={this.anyMethod} />
</div>
);
}
}
class Son extends PureComponent {
constructor(props) {
super(props);
}
render() {
const { componentDetails, anyMethod } = this.props;
console.log("Son -> render -> anyMethod", anyMethod);
console.log("Son -> render -> componentDetails", componentDetails);
return <div className="son">{componentDetails?.name}</div>;
}
}
export default Parent;
The main correct way to write this example is to pass the variable directly to the child component, because the reference to the variable is the same, so after checking by PureComponent, the reference has not changed, thus preventing the child component from rendering!!!
Note: Strictly speaking, this buggy example is a writing problem that causes re-rendering of subcomponents, so there's no talk of optimization, so let's forbid writing code like the buggy example!
Hooks Example
❎ Error Example Preview
❎ Error Example
import { useState, useEffect } from "react";
const Son = ({ componentDetails, anyMethod }) => {
useEffect(() => {
console.log("Son -> componentDetails", componentDetails);
}, [componentDetails]);
useEffect(() => {
console.log("Son -> anyMethod", anyMethod);
}, [anyMethod]);
return <div className="son">{componentDetails.name}</div>;
};
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<div className="parent">
<h5>Error Example</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
<Son componentDetails={{ name: "Sub-components" }} anyMethod={() => {}} />
</div>
);
};
export { Son, Parent };
In this error example, it is still a problem with the way props are passed! Next see how to correct it!
✅ Correct example 1
import { useState, useEffect } from "react";
const Son = ({ componentDetails, anyMethod }) => {
useEffect(() => {
console.log("Son -> componentDetails", componentDetails);
}, [componentDetails]);
useEffect(() => {
console.log("Son -> anyMethod", anyMethod);
}, [anyMethod]);
return <div className="son">{componentDetails.name}</div>;
};
// This is written for immutable values and can be passed like this
const componentDetails = { name: "Sub-components件" };
const anyMethod = () => {};
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
return (
<div className="parent">
<h5>Correct example 1</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
<Son componentDetails={componentDetails} anyMethod={anyMethod} />
</div>
);
};
export default Parent;
In this example, we simply refer the invariant value outside the component to ensure that the reference is unique and will not change as the component is updated. But there is a limitation to this way of writing. It is that it is only suitable for invariant values. But it also effectively avoids duplicate rendering of components.
✅ Correct example 2
import { useState, useEffect, useMemo, useCallback } from "react";
const Son = ({ componentDetails, anyMethod }) => {
useEffect(() => {
console.log("Son -> componentDetails", componentDetails);
}, [componentDetails]);
useEffect(() => {
console.log("Son -> anyMethod", anyMethod);
}, [anyMethod]);
return <div className="son">{componentDetails.name}</div>;
};
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((old) => old + 1);
};
const anyMethod = useCallback(() => {}, []);
const [componentDetails] = useMemo(() => {
const componentDetails = { name: "Sub-components" };
return [componentDetails];
}, []);
return (
<div className="parent">
<h5>Correct example 2</h5>
<p>Parent Component Count--{count}</p>
<button onClick={handleClick}>Add</button>
<Son componentDetails={componentDetails} anyMethod={anyMethod} />
</div>
);
};
export default Parent;
In this example, two optimization hooks, useCallback and useMemo , are used to determine whether to update a value change based on whether the dependency has changed to ensure that the value reference remains unchanged. This is suitable for most writes, but it should not be overused. Otherwise the code will be very confusing.
Context updates lead to component rendering
Class Example
❎ Error Example Preview
❎ Error Example
import React, { Component, createContext } from "react";
const contextValue = createContext(undefined);
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
handleIncrement:this.handleIncrement
};
}
handleIncrement = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
return (
<contextValue.Provider
value={this.state}
>
<div className="parent">
<h5>Error Example</h5>
<Son1 />
<contextValue.Consumer>
{(conProps) => <Son2 conProps={conProps} />}
</contextValue.Consumer>
</div>
</contextValue.Provider>
);
}
}
class Son1 extends Component {
constructor(props) {
super(props);
}
render() {
console.log("Subcomponent 1 is re-rendered!");
return <div className="son">Subassembly 1</div>;
}
}
class Son2 extends Component {
constructor(props) {
super(props);
}
render() {
console.log("Subcomponent 2 is re-rendered!");
const {
conProps: { count, handleIncrement },
} = this.props;
return (
<div className="son">
<p>Subassembly 2--{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
}
}
export { Parent };
In this example, if you look carefully, when you click the button in child component 2,t is the state of the parent component that changes so the problem is that the rendering of the parent component causes the child component to render as well. So how should we avoid duplicate rendering of the child component?
✅ Correct example 1
import React, { Component, createContext } from "react";
const contextValue = createContext(undefined);
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
handleIncrement:this.handleIncrement
};
}
handleIncrement = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
const { children } = this.props;
return (
<contextValue.Provider
value={this.state}
>
<div className="parent">
<h5>Correct example 1</h5>
{children}
<contextValue.Consumer>
{(conProps) => <Son2 conProps={conProps} />}
</contextValue.Consumer>
</div>
</contextValue.Provider>
);
}
}
class Son1 extends Component {
constructor(props) {
super(props);
}
render() {
console.log("Subcomponent 1 is re-rendered!");
return <div className="son">Subassembly 1</div>;
}
}
class Son2 extends Component {
constructor(props) {
super(props);
}
render() {
console.log("Subcomponent 2 is re-rendered!");
const {
conProps: { count, handleIncrement },
} = this.props;
return (
<div className="son">
<p>Subassembly 2--{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
}
}
export { Parent, Son1 };
<Parent>
<Son1 />
</Parent>
In this example, we still borrow the mechanism of children o render directly, so there is no Ract.createElement(Son) api execution in the parent component, and therefore no duplicate rendering!
✅ Correct example 2
import React, { Component, createContext, PureComponent } from "react";
const contextValue = createContext(undefined);
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
handleIncrement:this.handleIncrement
};
}
handleIncrement = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
return (
<contextValue.Provider
value={this.state}
>
<div className="parent">
<h5>Correct example 2</h5>
<Son1 />
<contextValue.Consumer>
{(conProps) => <Son2 conProps={conProps} />}
</contextValue.Consumer>
</div>
</contextValue.Provider>
);
}
}
class Son1 extends PureComponent {
constructor(props) {
super(props);
}
render() {
console.log("Subcomponent 1 is re-rendered!");
return <div className="son">Subcomponent 1</div>;
}
}
class Son2 extends PureComponent {
constructor(props) {
super(props);
}
render() {
console.log("Subcomponent 2 is re-rendered!");
const {
conProps: { count, handleIncrement },
} = this.props;
return (
<div className="son">
<p>Subcomponent 2--{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
}
}
export default Parent;
In this example, we mainly borrow the class PureComponent to help us perform the optimization automatically, so it is also possible to avoid duplicate rendering.
Note: Here you can also force the use of React.memo a bit.
Hooks Example
❎ Error Example Preview
❎ Error Example
import { createContext, useContext } from "react";
import { useCustomReducer } from "../useCustomizeContext";
const CustomizeContext = createContext(undefined);
const Son1 = () => {
console.log("Subcomponent 1 re-rendered!!!");
return <div className="son">子组件1</div>;
};
const Son2 = () => {
const { count, handleIncrement } = useContext(CustomizeContext);
console.log("Subcomponent 2 re-rendered!!!");
return (
<div className="son">
<p>Subcomponent 2-{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
};
const Parent = () => {
const value = useCustomReducer({ initValue: 1 });
return (
<CustomizeContext.Provider value={value}>
<div className="parent">
<h5>Error Example</h5>
<Son2 />
<Son1 />
</div>
</CustomizeContext.Provider>
);
};
export { Son1, Parent, Son2 };
In this example, the api's createContext,useContext,useReducer are used to implement a small Redux, and clicking on the button in child component 2 changes the count value, which in turn causes the value to change, so the parent component renders, causing the child component to follow suit.
✅ Correct example 1
import React from "react";
import {
CustomizeProvider,
useCustomizeContext,
useCustomReducer,
} from "../useCustomizeContext";
const Son1 = () => {
console.log("Subcomponent 1 re-rendered!!!");
return <div className="son">Subcomponent 1</div>;
};
const Son2 = () => {
const { count, handleIncrement } = useCustomizeContext();
console.log("Subcomponent 2 re-rendered!!!");
return (
<div className="son">
<p>Subcomponent 2-{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
};
const Parent = ({ children }) => {
const value = useCustomReducer({ initValue: 1 });
return (
<CustomizeProvider value={value}>
<div className="parent">
<h5>Correct example 1</h5>
<Son2 />
{children}
</div>
</CustomizeProvider>
);
};
export { Son1 };
export default Parent;
<Parent>
<Son1 />
</Parent>
In this example we are still using children to solve the duplicate rendering problem. This still works!
Description: In fact, you must use the right optimization in your project!
✅ Correct example 2
import React, { memo } from "react";
import {
CustomizeProvider,
useCustomizeContext,
useCustomReducer,
} from "../useCustomizeContext";
const Son1 = () => {
console.log("Subcomponent 1 re-rendered!!!");
return <div className="son">Subcomponent 1</div>;
};
const Son2 = () => {
const { count, handleIncrement } = useCustomizeContext();
console.log("Subcomponent 2 re-rendered!!!");
return (
<div className="son">
<p>Subcomponent 2-{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
};
// use memo
const MemoSon1 = memo(Son1);
const Parent = () => {
const value = useCustomReducer({ initValue: 1 });
return (
<CustomizeProvider value={value}>
<div className="parent">
<h5>Correct example 2</h5>
<Son2 />
<MemoSon1 />
</div>
</CustomizeProvider>
);
};
export default Parent;
The api memo is also used in this example, and is still the same, comparing whether the reference to the props has changed or not, and deciding whether to update it or not.
✅ Correct Example 3
import React, { useMemo } from "react";
import {
CustomizeProvider,
useCustomizeContext,
useCustomReducer,
} from "../useCustomizeContext";
const Son1 = () => {
console.log("Subcomponent 1 re-rendered!!!");
return <div className="son">Subcomponent 1</div>;
};
const Son2 = () => {
const { count, handleIncrement } = useCustomizeContext();
console.log("Subcomponent 2 re-rendered!!!");
return (
<div className="son">
<p>Subcomponent 2-{count}</p>
<button onClick={handleIncrement}>Add</button>
</div>
);
};
const Parent = () => {
const value = useCustomReducer({ initValue: 1 });
return (
<CustomizeProvider value={value}>
<div className="parent">
<h5>Correct Example 3</h5>
<Son2 />
{useMemo(
() => (
<Son1 />
),
[]
)}
</div>
</CustomizeProvider>
);
};
export default Parent;
In this example we still use the useMemo optimization hook to optimize the component.
🤙🤙🤙 Summary
The means of optimization in three cases are described in the article of this piece, mainly the use of.
- 🤙useMemo
- 🤙memo
- 🤙children
- 🤙useCallback
- 🤙PureComponent
- 🤙Extracting status components
- 🤙Extraction of constant values
These optimizations can be used in different situations, so you must use the appropriate optimizations if you are in the process of using them in conjunction with your code.
If you know other means of optimization can also be left in the comments section Oh!
Top comments (12)
Very impressive demonstration about the stress you get using REACT. What precisely was the advantage of using REACT?
Flexible! Including its powerful ecosystem! and community!
I used PureComponent for every class component. Is it a bad practice ? 😟
Yes, that is a bad practice what you're doing is premature optimization, and that is the root of all evil.
PureComponent optimize the performance when you have a performe issue; using it without an issue will hinder the performance because every component has to call shouldComponentUpdate and that doesn't happen for free. And functional components are not the answer either, they're not that much different from class components.
Okay, thanks! Man need a fix
Agree
I'll be the judge of that
I use memo for all function components but I always provide the comparison condition.
Thank you for the informative article!
I have heard about useMemo and useCallback before, but I never really got around to using them in my projects. I guess I caused unnecessary rendering.
Thanks for the message! These two Hooks also can not be used too often Oh! And look at the situation flexible use!
just a little question... what about prevSrate?
reactjs.org/docs/hooks-reference.h...
Sorry I didn't get your point!