React.memoを使用してパフォーマンスを最適化する方法

Nov 21 2020

Reactフロントエンドを使ってチェスゲームを作りました。元々、動きは一見瞬時に実装されていましたが、ヘッダーの背景を変更して順番を反映するなどの機能(白の動きに応じて白または黒の背景)やその他の同様の機能を追加した後、アプリの速度が著しく低下しました。私はそれがこれらのタイプの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")
    }

小道具を印刷するconsole.logは、移動ごとに8回(元々は2回)ステートメントを印刷するため、これが原因です。

私はクラスではなく機能コンポーネントを使用しており、最適化に関する研究から次の解決策にたどり着きました-

Reactフック-shouldComponentUpdateを実装するにはどうすればよいですか?

小道具を期待していない場合は、すべてのコンポーネントをReact.memo()でラップする必要がありますか?

ReactフックでshouldComponentUpdateを使用する方法は?

すべての単純なケースを指していReact.memoますが、それを実装しようとすると、多くの「小道具」が定義されていないエラー(多くの小道具を使用するたびに1つ)だけが表示されるため、明らかに何かが欠けています。

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;

このコンポーネントの小道具はターンが変わると変わるだけなので(props.status.white)、これは不要な再レンダリングを減らすのに良い場所だと思いましたが、私が見るすべての解決策は非常に単純です。このような小道具が広く使われている場合、React.memoを使用することはできませんか?

小道具へのアクセスを維持しながら、パフォーマンスを最適化するにはどうすればよいですか?

回答

4 JanCássio Nov 21 2020 at 02:31

まず、一般的なフロントエンドでのパフォーマンスのテストについて覚えておく必要があることです。

あなたは決してあなたがパフォーマンスをチェックしている間、パフォーマンスにアプリをたくさん減少、開くデベロッパーツールに活動をデベロッパーツールを残しません。

第二に、両方ともuseMemouseCallback徹底的にレンダリングしていない場合はパフォーマンスが向上しません。可能なレンダリング小道具は前にレンダリングされ、最も重要です。カスタムコンポーネント内に他のカスタムコンポーネントをレンダリングしていない場合は、おそらくレンダリングしません。それらのフックも使用する必要があります。

では、パフォーマンスを向上させるために、どの状況を使用する必要がuseCallbackありuseMemoますか?

これに答える前に、値タイプと参照タイプの違いを知っておく必要があります。

値の種類は不変であるため、所有権やガベージコレクターを気にすることなく、安全に割り当て、変更、使用できます。

JSのプリミティブリテラルタイプは値タイプです。

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

これは、2つの異なるアプローチを使用して同じ文字列を作成する方法の非常に単純な例です。

1つ目は、リテラル文字列を使用して文字列を作成することでした。次に、Stringクラスのインスタンスを使用します。

そのようなことをすると:

a === b // true

===比較aに対するではb

ただし、次のようなことを行うと、別のことが起こります。

c === d // true

これも戻りますtrueが、===ここでは動作が異なります。値を比較する代わりに、cの参照がdの参照と厳密に等しいことを比較しますcの値が演算子dを使用した場合の値と厳密に等しいかどうかを比較することはできません。===どちらもStringの参照タイプであるため、次のように両方を比較する必要があります。

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

考えてみようabメモリの参照を指していませんが、d参照と同じで作成された参照c

それでは、これを念頭に置いて、これらのフックがReactアプリのパフォーマンスを向上させる状況についての質問に戻りましょう。

ヘルプと比較REACT参考例えば関数、配列やオブジェクトの種類などの値は、我々が使用しuseCallbackたりuseMemoするためにカプセル化し比較した場合に決定することができますリアクト新しいタイプへのこれらの参照参照の値が変更されましたが。

したがって、階層の最下位レベルのみをレンダリングしている場合、おそらくこれらのフックはパフォーマンスの問題の解決に役立ちません。

ただし、参照タイプを処理するアプリの一部でそれを使用し、頻繁にレンダリングしている場合。これを使用する絶好の機会であり、Reactが、それらの参照が変更されているかどうか、レンダリングしないかどうか、残りまたは階層を認識できないようにするのに役立ちます。

履歴書では、useMemouseCallbackヘルプに使用されているが、いくつかのかどうかを確認するように反応する参照型のコンポーネントをレンダリングするために、その値を変更しました。