React performance optimization schemes are commonly used
React.lazy optimization of route switching
Use React. Lazy to load components dynamically to reduce performance waste
- The react. lazy function allows you to render dynamically imported components in the same way as regular components.
- See the official documentation: Code Splitting
Before use:
import OtherComponent from './OtherComponent';
Copy the code
After use:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
Copy the code
- Use: SRC \index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Link} from 'react-router-dom';
import {dynamic} from './utils';
const LoadingHome = dynamic(() = >import('./components/Home'));
const LoadingUser = dynamic(() = >import('./components/User'));
ReactDOM.render(
<Router>
<ul>
<li><Link to="/">Home</Link></li>
<li> <Link to="/user">User</Link></li>
</ul>
<Route path="/" exact={true} component={LoadingHome}/>
<Route path="/user" component={LoadingUser}/>
</Router>
,document.getElementById('root'));
Copy the code
- Core principle: SRC \utils.js
const Loading = () = > <div>Loading</div>;
export function dynamic(loadComponent) {
const LazyComponent = lazy(loadComponent)
return () = > (
<React.Suspense fallback={<Loading />} ><LazyComponent />
</React.Suspense>)}function lazy(load) {
return class extends React.Component {
state = { Component: null }
componentDidMount() {
load().then(result= > {
this.setState({ Component: result.default});
});
}
render() {
let { Component } = this.state;
return Component && <Component />; }}}Copy the code
Update phase optimization using PureComponent and Memo
- Class component usage
PureCompnent
The wrapper implements a shallow comparison of props to prevent repeated rendering of subcomponents if the shallowness is constant - Functional components can do the same with memo wraps
import React from 'react';
import {PureComponent,memo} from './utils';
export default class App extends React.Component{
constructor(props){
super(props);
this.state = {title:'counter'.number:0}
}
add = (amount) = >{
this.setState({number:this.state.number+amount});
}
render(){
console.log('App render');
return (
<div>
<Counter number={this.state.number}/>
<button onClick={()= >this.add(1)}>+1</button>
<button onClick={()= >this.add(0)}>+0</button>
<ClassTitle title={this.state.title}/>
<FunctionTitle title={this.state.title}/>
</div>)}}class Counter extends PureComponent{
render(){
console.log('Counter render');
return (
<p>{this.props.number}</p>)}}class ClassTitle extends PureComponent{
render(){
console.log('ClassTitle render');
return (
<p>{this.props.title}</p>)}}const FunctionTitle = memo(props= >{
console.log('FunctionTitle render');
return <p>{props.title}</p>;
});
Copy the code
- How PureComponent and Memo are implemented
import React from 'react';
export class PureComponent extends React.Component{
shouldComponentUpdate(nextProps,nextState){
return! shallowEqual(this.props,nextProps)||! shallowEqual(this.state,nextState)
}
}
export function memo(OldComponent){
return class extends PureComponent{
render(){
return <OldComponent {. this.props} / >}}}export function shallowEqual(obj1,obj2){
if(obj1 === obj2)
return true;
if(typeofobj1 ! = ='object' || obj1 ===null || typeofobj2 ! = ='object' || obj2 ===null) {return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if(keys1.length ! == keys2.length){return false;
}
for(let key of keys1){
if(! obj2.hasOwnProperty(key) || obj1[key]! == obj2[key]){return false; }}return true;
}
Copy the code
3, IMmutable to solve the memo shallow comparison trap
-
Because the memo uses Object.is(), only one layer (shallow comparison) can be compared. If the props are deeply nested, component changes cannot be identified. Therefore, deep comparison is often required. However, immutable. Js can be used to improve deep comparison efficiency.
-
Immutable. Js changes a reference object to an IMmutable object. When a property is changed, the current property and all of its parent properties are updated, while the remaining properties remain unchanged to achieve data reuse and improve deep comparison efficiency
import React from 'react';
import {PureComponent} from './utils';
+import { Map } from "immutable";
export default class App extends React.Component{
constructor(props){
super(props);
+ this.state = {count:Map({ number: 0 })}
}
add = (amount) = >{+let count = this.state.count.set('number'.this.state.count.get('number') + amount);
+ this.setState({count});
}
render(){
console.log('App render');
return (
<div>
<Counter number={this.state.count.get('number')} / >
<button onClick={()= >this.add(1)}>+1</button>
<button onClick={()= >this.add(0)}>+0</button>
</div>)}}class Counter extends PureComponent{
render(){
console.log('Counter render');
return (
<p>{this.props.number}</p>)}}Copy the code
4. Use FixedSizeList of React – Window to optimize rendering with large data volume
-
When rendering a large amount of data, we usually use a virtual list scheme for optimization
-
Use an array to store the positions of all list elements. Render only list elements in the visual area. When the visual area is scrolling, calculate which elements should be rendered in the visual area based on the offset size of the scroll and the positions of all list elements
-
The react – window [www.npmjs.com/package/rea]…
-
Fixed – size [react – window. Now. Sh / # / examples /…
-
The react – virtualized [react – window. Now. Sh / # / examples /…
Example Use the react-window command
import React, { Component, lazy, Suspense } from "react";
import ReactDOM from "react-dom";
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) = > (
<div style={style}>Row {index}</div>
);
const Container = () = > (
<List
height={150}
itemCount={1000}
itemSize={35}
width={300}
>
{Row}
</List>
);
ReactDOM.render(<Container/>.document.querySelector("#root"));
Copy the code
Custom virtual list
- src\index.js
import React from 'react';
import { render } from 'react-dom';
//import VirtualList from 'react-tiny-virtual-list';
import VirtualList from './components/VirtualList';
const data = new Array(30).fill(0);
render(
<VirtualList
width='50%'
height={500}
itemCount={data.length}
itemSize={50}
renderItem={(data)= > {
let { index, item, style } = data;
console.log(data);
return (
<div key={index} style={{ . style.backgroundColor: index % 2= = =0 ? 'green' : 'orange' }}>
{index+1}
</div>)}} / >.document.getElementById('root'));Copy the code
- Implementation principle of VirtualList. Js
- src\components\VirtualList.js
import React from 'react';
export default class Index extends React.Component {
scrollBox = React.createRef()
state = {start: 0}
handleScroll = () = > {
const { itemSize } = this.props;
const { scrollTop } = this.scrollBox.current;
const start = Math.floor(scrollTop / itemSize);
this.setState({start})
}
render() {
const { height, width, itemCount, itemSize, renderItem } = this.props;
const { start } = this.state;
let end = start + Math.floor(height/itemSize)+1; end = end>itemCount? itemCount:end;const visibleList = new Array(end - start).fill(0).map((item,index) = >({index:start+index}));
const style = {position:'absolute'.top:0.left:0.width:'100%'.height: itemSize};
return (
<div
style={{overflow: 'auto',willChange:'transform', height.width}}
ref={this.scrollBox}
onScroll={this.handleScroll}
>
<div style={{position: 'absolute',width:'100% ',height:` ${itemCount * itemSize}px`}} >{ visibleList.map(({index}) => renderItem({ index, style:{... style,top:itemSize*index} })) }</div>
</div>)}}Copy the code
Use useImmer to optimize performance in React hooks
Using useMemo to cache expensive calculations
const memoizedValue = useMemo(() = > computeExpensiveValue(a, b), [a, b]);
Copy the code
-
Returns an memoized value.
-
You pass in the create function and the dependency array as arguments to useMemo, which recalculates memoized values only when a dependency changes. This optimization helps avoid costly calculations every time you render.
useuseImmerState
Processing shared data
- Example 1
import React from 'react';
import ReactDOM from 'react-dom';
import {useImmerState} from './immer'
let id = 1;
function Todos() {
const [todos, setTodos] = useImmerState({
list: []})const addTodo = () = > setTodos((draft) = > {
draft.list.push(id++)
})
return (
<>
<button onClick={addTodo}>increase</button>
<ul>
{
todos.list.map((item, index) => <li key={index}>{item}</li>)}</ul>
</>
)
}
ReactDOM.render(
<Todos />.document.getElementById('root'));Copy the code
Using useImmer library
import React from "react";
import { useImmer } from "use-immer";
function App() {
const [person, updatePerson] = useImmer({
name: "Michel".age: 33
});
function updateName(name) {
updatePerson(draft= > {
draft.name = name;
});
}
function becomeOlder() {
updatePerson(draft= > {
draft.age++;
});
}
return (
<div className="App">
<h1>
Hello {person.name} ({person.age})
</h1>
<input
onChange={e= > {
updateName(e.target.value);
}}
value={person.name}
/>
<br />
<button onClick={becomeOlder}>Older</button>
</div>
);
}
Copy the code
Use useRef to cache invariant values for each render
useRef
This can be used as the this context of the class component, and useRef variables are updated without updating the view layer, as is the case with the this variable in the class component.
const countdown = useRef(0)
const countdownHandler = () = > {
if (countdown.current === 10) return
setTimeout(() = > {
countdown.current += 1
countdownHandler()
}, 1000)}Copy the code
Other rendering schemes
- Skeleton screen
- pre-rendered
- Lazy loading of images
Vii. Practical thinking about performance optimization
- Not all function components are wrapped in memo as a performance optimization!
If a child component is overly dependent on the state of the parent component, then it makes little sense to wrap the Memo around that child component, but the Memo itself takes time to calculate comparisons. Then, if a child component rerenders a large percentage of times following its parent, the extra Memo comparison time becomes a burden, even if it is very short.
- Don’t rely too much on useMemo
UseMemo itself is also expensive, because the memory function itself extracts the dependency from the dependency array and compares it with the value recorded last time. If the value is equal, the calculation cost will be saved. Otherwise, the callback needs to be executed again, which consumes some memory and computing resources.
So, when to use useMemo, think about the following two questions?
- Are the functions passed to useMemo expensive?
Some business scenarios will be very expensive to compute, so we need to cache the last value to avoid recalculation every time the parent component is re-rendered. If it’s not that expensive, then useMemo itself probably outweighs the time saved
- Is the computed value type complex?
If a complex type (object, array) is returned, useMemo can be used because every rerender generates a new reference, even if the value does not change, causing the child component to be rerendered. If the parent component uses useMemo to calculate values of primitive types, then the child component uses the Memo to make it easier to avoid rerendering and use useMemo