Come utilizzare React.memo per ottimizzare le prestazioni
Ho realizzato una partita a scacchi utilizzando un frontend React. In origine, tutte le mosse venivano implementate in modo apparentemente istantaneo, ma dopo aver aggiunto funzionalità extra come la modifica dello sfondo dell'intestazione per riflettere di chi è il turno (sfondo bianco o nero a seconda che si muova del bianco) e altre funzionalità simili, la mia app è notevolmente rallentata. Sospetto che siano questi tipi di dichiarazioni if-
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")
}
che sono responsabili perché il console.log che stampa gli oggetti di scena stamperà la sua dichiarazione 8 volte per mossa (originariamente era due volte)
Sto usando componenti funzionali piuttosto che classi e la mia ricerca sull'ottimizzazione mi ha portato alle seguenti soluzioni:
React Hooks: come implementare shouldComponentUpdate?
Devo avvolgere tutti i miei componenti con React.memo () se non si aspetta alcun oggetto di scena?
Come usare shouldComponentUpdate con React Hooks?
tutto punta a casi semplici di React.memoma ovviamente mi manca qualcosa perché quando tento di implementarlo, tutto quello che ottengo sono una pletora di "oggetti di scena" non sono errori definiti (uno per ogni volta che uso oggetti di scena che è molto).
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;
Poiché gli oggetti di scena in questo componente cambiano solo quando cambia la svolta (props.status.white), ho pensato che questo sarebbe stato un buon posto per cercare di ridurre i ri-rendering non necessari, ma tutte le soluzioni che vedo sono molto semplici. Non è possibile utilizzare React.memo quando c'è un uso diffuso di oggetti di scena come questo?
Come ottimizzo le prestazioni pur mantenendo l'accesso agli oggetti di scena?
Risposte
Innanzitutto, qualcosa che devi tenere a mente sul test delle prestazioni nel frontend in generale.
Non lasci mai devtools aperti mentre controlli le prestazioni, le attività nei devtools, diminuisci molto le prestazioni della tua app.
In secondo luogo, entrambi useMemoe useCallbacknon ti daranno alcun miglioramento delle prestazioni se non stai eseguendo il rendering in modo esaustivo, gli oggetti di scena che sono possibili vengono renderizzati prima e, cosa più importante, se non stai eseguendo il rendering di altri componenti personalizzati all'interno del tuo componente personalizzato, probabilmente non lo fai devi usare anche quei ganci.
Quindi, quale situazione dovremmo usare useCallbacke useMemo, per migliorare le prestazioni?
Prima di rispondere a questa domanda, dovresti conoscere la differenza tra tipi di valore e tipi di riferimento.
I tipi di valore sono immutabili, quindi puoi assegnarli, modificarli e utilizzarli in sicurezza senza preoccuparti della proprietà e del garbage collector.
I tipi di letterali primitivi in JS sono tipi di valore:
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
Questo è un esempio molto semplice di come possiamo creare la stessa stringa utilizzando due approcci diversi.
La prima è stata la creazione di una stringa utilizzando una stringa letterale. Secondo utilizzando un'istanza della classe String.
Se facciamo qualcosa del genere:
a === b // true
Il ===confronta il valore del arispetto al valore in b.
Tuttavia, accade qualcosa di diverso quando facciamo qualcosa del genere:
c === d // true
Anche questo ritorna truema ===qui funziona in modo diverso, invece di confrontare i valori, confronta il riferimento di c è strettamente uguale al riferimento di d . Non possiamo confrontare se il valore di cè strettamente uguale al valore del dsolo utilizzo di ===operator, poiché entrambi sono tipi di riferimento di String, dovremmo confrontarli entrambi in questo modo:
// toString() in this case works as well but valueOf is more semantic in this case
c.valueOf() === d.valueOf()
Pensa ae bnon indica un riferimento in memoria, ma un driferimento allo stesso riferimento creato in c.
Quindi ora, con questo in mente, torniamo alla domanda su quale situazione quegli hook migliorano le prestazioni di un'app React.
Per aiutare React a confrontare i valori di riferimento , come ad esempio i tipi Function, Array e Object, utilizziamo useCallbacko useMemoper incapsulare quei riferimenti in un nuovo tipo che React può confrontare e decidere se il valore del riferimento è stato modificato.
Quindi, se stai eseguendo il rendering solo del livello più basso della tua gerarchia, probabilmente quegli hook non ti aiuteranno a risolvere i problemi di prestazioni.
Tuttavia, se lo usi in una parte della tua app che si occupa di tipi di riferimento e lo stai renderizzando frequentemente. È una bella opportunità per usarlo e aiuta React a sapere se quei riferimenti sono stati modificati o meno per rendere o meno il resto o la gerarchia.
In resume, useMemoe useCallbacksono usati per aiutare React a scoprire se qualche tipo di riferimento ha cambiato il suo valore per rendere il componente.