Jak używać React.memo do optymalizacji wydajności

Nov 21 2020

Zrobiłem partię szachów przy użyciu interfejsu React. Początkowo wszelkie ruchy były zaimplementowane pozornie natychmiast, ale po dodaniu dodatkowych funkcji, takich jak zmiana tła nagłówka, aby odzwierciedlić, czy to jest kolej (białe lub czarne tło w zależności od tego, czy poruszają się białe) i inne podobne funkcje, moja aplikacja wyraźnie spowolniła. Podejrzewam, że to tego typu stwierdzenia 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")
    }

które są odpowiedzialne, ponieważ console.log drukując rekwizyty wypisze instrukcję 8 razy na ruch (pierwotnie było to dwa razy)

Korzystam raczej z komponentów funkcjonalnych niż z zajęć, a moje badania nad optymalizacją doprowadziły mnie do następujących rozwiązań:

React Hooks - Jak zaimplementować shouldComponentUpdate?

Czy powinienem owinąć wszystkie moje komponenty za pomocą React.memo (), jeśli nie oczekuje żadnych rekwizytów?

Jak korzystać z shouldComponentUpdate z hakami React?

wszystko wskazuje na proste przypadki, React.memoale najwyraźniej czegoś mi brakuje, ponieważ kiedy próbuję to wdrożyć, wszystko, co otrzymuję, to mnóstwo „rekwizytów” to nie zdefiniowane błędy (jeden za każdym razem, gdy używam rekwizytów, a to dużo).

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;

Ponieważ rekwizyty w tym komponencie zmieniają się tylko wtedy, gdy zmienia się kolej (props.status.white), pomyślałem, że byłoby to dobre miejsce, aby spróbować ograniczyć niepotrzebne ponowne renderowanie, ale wszystkie rozwiązania, które widzę, są bardzo proste. Czy nie można używać React.memo, gdy takie rekwizyty są powszechnie używane?

Jak zoptymalizować wydajność, jednocześnie zachowując dostęp do rekwizytów?

Odpowiedzi

4 JanCássio Nov 21 2020 at 02:31

Po pierwsze, coś, o czym musisz pamiętać podczas testowania wydajności w ogóle.

Nigdy nie pozostawiasz otwartych devtools podczas sprawdzania wydajności, działań w devtools, znacznie zmniejszając wydajność aplikacji.

Po drugie, jedno useMemoi drugie useCallbacknie poprawi wydajności, jeśli nie renderujesz wyczerpująco, renderowanie rekwizytów, które są możliwe, są renderowane wcześniej i co najważniejsze, jeśli nie renderujesz innych niestandardowych komponentów wewnątrz swojego niestandardowego komponentu, prawdopodobnie nie. trzeba też użyć tych haków.

Więc z jakiej sytuacji powinniśmy skorzystać useCallbacki useMemoaby poprawić wydajność?

Zanim odpowiesz, powinieneś znać różnicę między typami wartości i typami odwołań.

Typy wartości są niezmienne, więc możesz je bezpiecznie przypisywać, modyfikować i używać bez martwienia się o własność i zbieracz elementów bezużytecznych.

Prymitywne typy literałów w JS to typy wartości:

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

To jest bardzo prosty przykład tego, jak możemy utworzyć ten sam ciąg przy użyciu dwóch różnych podejść.

Pierwszym było utworzenie łańcucha przy użyciu literału. Po drugie przy użyciu instancji klasy String.

Jeśli zrobimy coś takiego:

a === b // true

===Porównuje wartości w astosunku do wartości w b.

Jednak coś innego dzieje się, gdy robimy coś takiego:

c === d // true

To również zwraca, trueale ===działa tutaj inaczej, zamiast porównywać wartości, porównuje odwołanie do c jest ściśle równe odwołaniu do d . Nie możemy porównywać, jeśli wartość cjest ściśle równa wartości dsamego użycia ===operatora, ponieważ oba są typami referencyjnymi String, powinniśmy porównać oba w ten sposób:

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

Pomyśl ai bnie wskaż odniesienia w pamięci, ale dodniesienie do tego samego odniesienia utworzonego w c.

Mając to na uwadze, wróćmy do pytania, w jakiej sytuacji te hooki poprawiają wydajność aplikacji React.

Aby pomóc Reactowi porównać wartości referencyjne , takie jak na przykład typy Function, Array i Object, używamy useCallbacklub w useMemocelu hermetyzacji tych odwołań do nowego typu, który React może porównać i zdecydować, czy wartość odniesienia została zmieniona.

Tak więc, jeśli renderujesz tylko najniższy poziom hierarchii, prawdopodobnie te punkty zaczepienia nie pomogą Ci rozwiązać problemów z wydajnością.

Jeśli jednak używasz tego w części aplikacji, która zajmuje się typami referencyjnymi, i często ją renderujesz. To dobra okazja, aby to wykorzystać i pomaga Reactowi wiedzieć, czy te odniesienia zostały zmienione, czy nie, aby renderować lub nie resztę lub hierarchię.

W wznowieniu, useMemoi useCallbacksą używane, aby pomóc Reactowi dowiedzieć się, czy jakiś typ referencyjny zmienił swoją wartość, aby renderować komponent.