简体   繁体   中英

How to use React.memo to optimize performance

I made a chess game using a React frontend. Originally, any moves were implemented seemingly instantaneously but after adding extra features like changing the background of the Header to reflect whose turn it is (white or black background depending on if its white's move) and other similar features, my app has noticeably slowed down. I suspect it's these types of if statements-

const Details = props => {
    console.log(props.status);
    let [backGround, setBackGround] = useState("w details")
    const history = useHistory();

    if (props.status.white && backGround === "bl details"){
        setBackGround("w details")
    } else if (!props.status.white && backGround === "w details"){
        setBackGround("bl details")
    }

that are responsible because the console.log printing the props will print its statement 8 times per move(originally it was twice)

I am using functional components rather than classes and my research on optimization led me to the following solutions-

React Hooks - How do I implement shouldComponentUpdate?

Should I wrap all my components with React.memo() if it is not expecting any props?

How to use shouldComponentUpdate with React Hooks?

all pointing to simple cases of React.memo but I am obviously missing something because when I attempt to implement it, all I get are a plethora of 'props' is not defined errors (one for each time I use props which is a lot).

Details.jsx

import React, { useState } from 'react';
import "../App.css"
import DataService from '../service/DataService';
import { useHistory } from 'react-router-dom';

let [backGround, setBackGround] = useState("w details")
const Details = React.memo(props => {if (props.status.white){setBackGround("w details")} else {setBackGround("bl details")}}, (prevProps, props) => prevProps.white === props.white);  {
    console.log(props.status);
    
    const history = useHistory();

    const undo = () => {
        DataService.undo()
        .then(res => {
            console.log(res);
            props.setTheBoard(res.data);
            props.changeTurn();
        })
        .catch(err => {
            console.log(err);
            window.alert(err.response.data.errMessage)
        })
    }
    
    const restart = () => {
        DataService.restartGame()
        .then(res => {
            console.log(res);
            props.setTheBoard(res.data);
            props.changeTurn(true);
        })
        .catch(err => {
            console.log(err);
            window.alert(err.response.data.errMessage)
        })
    }

    const newGame = () => {
        history.push('/');
    }

    return ( 
        <div className={backGround} >  
            {props.status.active ? <h2>It is {props.status.playerName}'s turn</h2> :
            <div>           
                <h1 className="check">Game Over!</h1>
                <button className="tooltip" onClick={restart}>RESTART<span className="tooltiptext">Play another game vs the same opponent</span></button>
                <button className="tooltip" onClick={newGame}>NEW GAME<span className="tooltiptext">Play a game vs a different opponent</span></button>
            </div>}                          
                       
                           
            {props.status.active &&
            <div>                
                {props.isMove ? <button className="detailButtons" onClick={props.specialMove}>Special Move</button> : <button className="detailButtons" onClick={() => props.endTheGame(true)}>Forfeit</button> }
                {props.isMove ? <button className="detailButtons" onClick={props.unselect}>Unselect Piece</button> : <button className="detailButtons" onClick={() => props.endTheGame(false)}>Draw</button> } 
                {props.isMove ? <button className="detailButtons">Toggle Sidebar</button> : props.undo && <button className="detailButtons" onClick={() => undo()}>Undo</button> }                
                {props.status.check && <h1 className="check">You must move out of check!</h1>}                
            </div> }
            
        </div>
     );
}
 
export default Details;

Since the props in this component only changes when the turn changes (props.status.white) I thought this would be a good place to try to cut down on the unnecessary re-renders but all the solutions I see for it are very simple. Is it not possible to use React.memo when there is a widespread use of props like this?

How do I optimize the performance while still keeping access to props?

First, something you have to keep in mind about testing performance in frontend in general.

You never leave devtools open while you're checking performance, activities into the devtools, decrease a lot the performance your app.

Second, both useMemo and useCallback won't give you any performance improvement if you are not rendering exhaustively, rendering props that possible are rendered before and most important, if you aren't rendering other custom components inside your custom component, probably, you don't need to use those hooks too.

So, which situation should we use useCallback and useMemo , to improve performance?

Before to answer that, you should know the difference between value types, and reference types.

Value Types are immutable, so you can safe assign, modify and use it without worry about ownership and garbage collector.

The primitive literals types in JS are value types:

let a = "Hello"
let b = a
// from here, b doesn't own a reference, because a is a value type

let c = new String("Hello")
let d = c
// now d owns c reference because c was initialized as an instance of String class

This is a very simple example of how we can create the same string using two different approaches.

The first was creating a string using literal string. Second using a instance of String class.

If we do something like that:

a === b // true

The === compares the value of a against the the value in b .

However, something different happens when we do something like that:

c === d // true

This also return true but the === works different here, instead of compare values, it compare the reference of c is strictly equals to the reference of d . We can't compare if the value of c is strictly equals to the value of d just using === operator, since both are reference types of String, we should compare both like that:

// toString() in this case works as well but valueOf is more semantic in this case
c.valueOf() === d.valueOf()

Think about a and b doesn't point to a reference in memory, but d reference to the same reference created in c .

So now, with this in mind, let's go back to the question about which situation those hooks improves the performance of a React app.

To help React compare reference values, such as Function, Array and Object types for example, we use useCallback or useMemo to encapsulate those references into a new type that React can compare and decide if the reference's value was changed.

So, if you are rendering just the lowest level of your hierarchy, probably those hooks will not help you solving performance issues.

However, if you use that in a part of your app that deals with reference types and you're rendering it frequently. It's a nice opportunity to use that and helps React to know if those references has been changed or not to render or not the rest or hierarchy.

In resume, useMemo and useCallback are used to help React to find out if some reference type has changed its value to render the component.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM