序章

React アプリケーションでは、パフォーマンスの問題は、ネットワークレイテンシ、過負荷のAPI、非効率的なサードパーティライブラリ、さらには異常に大きな負荷が発生するまで正常に機能する適切に構造化されたコードに起因する可能性があります。 パフォーマンスの問題の根本原因を特定するのは難しい場合がありますが、これらの問題の多くはコンポーネントの再レンダリングに起因します。 コンポーネントが予想以上に再レンダリングするか、コンポーネントにデータ量の多い操作があり、各レンダリングが遅くなる可能性があります。 このため、不要な再レンダリングを防ぐ方法を学ぶことで、Reactアプリケーションのパフォーマンスを最適化し、ユーザーのエクスペリエンスを向上させることができます。

このチュートリアルでは、Reactコンポーネントのパフォーマンスの最適化に焦点を当てます。 問題を調査するために、テキストのブロックを分析するコンポーネントを作成します。 さまざまなアクションによって再レンダリングがトリガーされる方法と、フックおよびメモ化を使用してコストのかかるデータ計算を最小限に抑える方法について説明します。 このチュートリアルの終わりまでに、次のような多くのパフォーマンス向上フックに精通するようになります。 useMemouseCallback フック、およびそれらを必要とする状況。

前提条件

ステップ1—再レンダリングの防止 memo

このステップでは、コンポーネントを分析するテキストを作成します。 テキストのブロックを取得するための入力と、文字と記号の頻度を計算するコンポーネントを作成します。 次に、テキストアナライザーのパフォーマンスが低下するシナリオを作成し、パフォーマンスの問題の根本的な原因を特定します。 最後に、Reactを使用します memo 親が変更されたときにコンポーネントで再レンダリングされないようにする機能ですが、子コンポーネントへのpropsは変更されません。

このステップを完了すると、チュートリアルの残りの部分で使用する作業コンポーネントが完成し、親の再レンダリングによって子コンポーネントのパフォーマンスの問題がどのように発生するかを理解できます。

テキストアナライザーの構築

開始するには、を追加します <textarea> 要素に App.js.

開ける App.js 選択したテキストエディタで:

  1. nano src/components/App/App.js

次に、 <textarea>で入力 <label>. ラベルを内側に配置します <div> とともに classNamewrapper 次の強調表示されたコードを追加します。

パフォーマンス-チュートリアル/src/components/App/App.js
import React from 'react';
import './App.css';

function App() {
  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Add Your Text Here:</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
        >
        </textarea>
      </label>
    </div>
  )
}

export default App;

これにより、サンプルアプリケーションの入力ボックスが作成されます。 ファイルを保存して閉じます。

開ける App.css いくつかのスタイルを追加するには:

  1. nano src/components/App/App.css

中身 App.css、パディングを追加します .wrapper クラスを追加し、 margindiv 要素。 CSSを次のように置き換えます。

パフォーマンス-チュートリアル/src/components/App/App.css

.wrapper {
    padding: 20px;
}

.wrapper div {
    margin: 20px 0;
}

これにより、入力とデータ表示が分離されます。 ファイルを保存して閉じます。

次に、のディレクトリを作成します CharacterMap 成分。 このコンポーネントは、テキストを分析し、各文字と記号の頻度を計算し、結果を表示します。

まず、ディレクトリを作成します。

  1. mkdir src/components/CharacterMap

次に開きます CharacterMap.js テキストエディタの場合:

  1. nano src/components/CharacterMap/CharacterMap.js

内部に、というコンポーネントを作成します CharacterMap それはかかります text 小道具として、 text<div>:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React from 'react';
import PropTypes from 'prop-types';

export default function CharacterMap({ text }) {
  return(
    <div>
      Character Map:
      {text}
    </div>
  )
}

CharacterMap.propTypes = {
  text: PropTypes.string.isRequired
}

PropTypeを追加していることに注意してください。 text 弱いタイピングを導入するための小道具。

テキストをループして文字情報を抽出する関数を追加します。 関数に名前を付ける itemize を渡す text 引数として。 The itemize 関数はすべての文字を数回ループし、テキストサイズが大きくなると非常に遅くなります。 これにより、パフォーマンスをテストするための良い方法になります。

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  const letters = text.split('')
    .filter(l => l !== ' ')
    .reduce((collection, item) => {
      const letter = item.toLowerCase();
      return {
        ...collection,
        [letter]: (collection[letter] || 0) + 1
      }
    }, {})
  return letters;
}

export default function CharacterMap({ text }) {
  return(
    <div>
      Character Map:
      {text}
    </div>
  )
}

CharacterMap.propTypes = {
  text: PropTypes.string.isRequired
}

中身 itemize、すべての文字で .split を使用して、テキストをarrayに変換します。 次に、 .filterメソッドを使用してスペースを削除し、.reduceメソッドを使用して各文字を繰り返し処理します。 内部 .reduce メソッドでは、 object を初期値として使用し、文字を小文字に変換して追加することにより、文字を正規化します。 1 前の合計にまたは 0 以前の合計がなかった場合。 オブジェクトスプレッド演算子を使用して以前の値を保持しながら、オブジェクトを新しい値で更新します。

各文字のカウントを持つオブジェクトを作成したので、最も高い文字でオブジェクトを並べ替える必要があります。 オブジェクトをペアの配列に変換します Object.entries. 配列の最初の項目は文字で、2番目の項目はカウントです。 .sort メソッドを使用して、最も一般的な文字を上に配置します。

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  const letters = text.split('')
    .filter(l => l !== ' ')
    .reduce((collection, item) => {
      const letter = item.toLowerCase();
      return {
        ...collection,
        [letter]: (collection[letter] || 0) + 1
      }
    }, {})
  return Object.entries(letters)
    .sort((a, b) => b[1] - a[1]);
}

export default function CharacterMap({ text }) {
  return(
    <div>
      Character Map:
      {text}
    </div>
  )
}

CharacterMap.propTypes = {
  text: PropTypes.string.isRequired
}

最後に、 itemize テキストで機能し、結果を表示します。

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js

import React from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  const letters = text.split('')
    .filter(l => l !== ' ')
    .reduce((collection, item) => {
      const letter = item.toLowerCase();
      return {
        ...collection,
        [letter]: (collection[letter] || 0) + 1
      }
    }, {})
  return Object.entries(letters)
    .sort((a, b) => b[1] - a[1]);
}

export default function CharacterMap({ text }) {
  return(
    <div>
      Character Map:
      {itemize(text).map(character => (
        <div key={character[0]}>
          {character[0]}: {character[1]}
        </div>
      ))}
    </div>
  )
}

CharacterMap.propTypes = {
  text: PropTypes.string.isRequired
}

ファイルを保存して閉じます。

次に、コンポーネントをインポートして、内部にレンダリングします App.js. 開ける App.js:

  1. nano src/components/App/App.js

コンポーネントを使用する前に、テキストを保存する方法が必要です。 useState をインポートしてから関数を呼び出し、値をという変数に格納します。 text と呼ばれる更新関数 setText.

を更新するには text 、に関数を追加します onChange それは合格します event.target.valuesetText 関数:

パフォーマンス-チュートリアル/src/components/App/App.js
import React, { useState } from 'react';
import './App.css';

function App() {
  const [text, setText] = useState('');

  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Your Text</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
          onChange={event => setText(event.target.value)}
        >
        </textarea>
      </label>
    </div>
  )
}

export default App;

初期化していることに注意してください useState 空の文字列で。 これにより、に渡す値が確実になります CharacterMap ユーザーがまだテキストを入力していなくても、コンポーネントは常に文字列です。

輸入 CharacterMap 後にレンダリングします <label> エレメント。 合格 text に状態 text 小道具:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { useState } from 'react';
import './App.css';

import CharacterMap from '../CharacterMap/CharacterMap';

function App() {
  const [text, setText] = useState('');

  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Your Text</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
          onChange={event => setText(event.target.value)}
        >
        </textarea>
      </label>
      <CharacterMap text={text} />
    </div>
  )
}

export default App;

ファイルを保存します。 これを行うと、ブラウザが更新され、テキストを追加すると、入力後に文字分析が表示されます。

例に示されているように、コンポーネントは少量のテキストでかなりうまく機能します。 キーストロークごとに、Reactは CharacterMap 新しいデータで。 ただし、ローカルでのパフォーマンスは誤解を招く可能性があります。 すべてのデバイスが開発環境と同じメモリを備えているわけではありません。

パフォーマンスのテスト

アプリケーションのパフォーマンスをテストする方法は複数あります。 大量のテキストを追加することも、使用するメモリを減らすようにブラウザを設定することもできます。 コンポーネントをパフォーマンスのボトルネックにプッシュするには、GNUWikipediaエントリをコピーして、テキストボックスに貼り付けます。 ウィキペディアのページの編集方法によって、サンプルが若干異なる場合があります。

エントリをテキストボックスに貼り付けたら、追加の文字を入力してみてください e 表示にかかる時間に注意してください。 文字コード表が更新される前に、かなりの一時停止があります。

コンポーネントの速度が十分でなく、 Firefox Edge 、またはその他のブラウザを使用している場合は、速度が低下するまでテキストを追加してください。

Chrome を使用している場合は、[パフォーマンス]タブ内でCPUを調整できます。 これは、スマートフォンや古いハードウェアをエミュレートするのに最適な方法です。 詳細については、ChromeDevToolsのドキュメントをご覧ください。

ウィキペディアのエントリでコンポーネントが遅すぎる場合は、一部のテキストを削除してください。 目立った遅延を受け取りたいが、それを異常に遅くしたり、ブラウザをクラッシュさせたりしたくない。

子コンポーネントの再レンダリングの防止

The itemize 関数は、前のセクションで特定された遅延のルートです。 この関数は、コンテンツを数回ループすることにより、各エントリに対して多くの作業を実行します。 関数自体で直接実行できる最適化がありますが、このチュートリアルの焦点は、テキストが変更されていないときにコンポーネントの再レンダリングを処理する方法です。 言い換えれば、あなたは itemize 変更するためのアクセス権がない機能として機能します。 目標は、必要な場合にのみ実行することです。 これは、制御できないAPIまたはサードパーティライブラリのパフォーマンスを処理する方法を示しています。

まず、親は変更されますが、子コンポーネントは変更されない状況を調べます。

の中に App.js、コンポーネントの動作を説明する段落と、情報を切り替えるボタンを追加します。

パフォーマンス-チュートリアル/src/components/App/App.js
import React, { useReducer, useState } from 'react';
import './App.css';

import CharacterMap from '../CharacterMap/CharacterMap';

function App() {
  const [text, setText] = useState('');
  const [showExplanation, toggleExplanation] = useReducer(state => !state, false)

  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Your Text</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
          onChange={event => setText(event.target.value)}
        >
        </textarea>
      </label>
      <div>
        <button onClick={toggleExplanation}>Show Explanation</button>
      </div>
      {showExplanation &&
        <p>
          This displays a list of the most common characters.
        </p>
      }
      <CharacterMap text={text} />
    </div>
  )
}

export default App;

電話する useReducer レデューサー機能付きのフックで現在の状態を反転させます。 出力をに保存します showExplanationtoggleExplanation. 後に <label>、説明と次の場合に表示される段落を切り替えるボタンを追加します showExplanation 真実です。

ファイルを保存して終了します。 ブラウザが更新されたら、ボタンをクリックして説明を切り替えます。 遅延があることに注意してください。

これには問題があります。 ユーザーが少量のJSXを切り替えているときに、遅延が発生することはありません。 親コンポーネントが変更されると、遅延が発生します。App.js この状況では— CharacterMap コンポーネントは、文字データを再レンダリングおよび再計算しています。 The text propは同じですが、親が変更されたときにReactがコンポーネントツリー全体を再レンダリングするため、コンポーネントは引き続き再レンダリングされます。

ブラウザの開発者ツールを使用してアプリケーションのプロファイルを作成すると、親が変更されたためにコンポーネントが再レンダリングされることがわかります。 開発者ツールを使用したプロファイリングのレビューについては、 ReactDeveloperToolsを使用してReactコンポーネントをデバッグする方法を確認してください。

以来 CharacterMap 高価な関数が含まれているため、小道具が変更された場合にのみ再レンダリングする必要があります。

開ける CharacterMap.js:

  1. nano src/components/CharacterMap/CharacterMap.js

次に、インポート memo、次にコンポーネントをに渡します memo 結果をデフォルトとしてエクスポートします。

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { memo } from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  ...
}

function CharacterMap({ text }) {
  return(
    <div>
      Character Map:
      {itemize(text).map(character => (
        <div key={character[0]}>
          {character[0]}: {character[1]}
        </div>
      ))}
    </div>
  )
}

CharacterMap.propTypes = {
  text: PropTypes.string.isRequired
}

export default memo(CharacterMap);

ファイルを保存します。 これを行うと、ブラウザがリロードされ、ボタンをクリックしてから結果が得られるまでの遅延はなくなります。

開発者ツールを見ると、コンポーネントが再レンダリングされていないことがわかります。

The memo 関数は小道具の浅い比較を実行し、小道具が変更されたときにのみ再レンダリングします。 浅い比較では、 === 前の小道具を現在の小道具と比較する演算子。

比較は無料ではないことを覚えておくことが重要です。 小道具をチェックするにはパフォーマンスコストがかかりますが、計算に費用がかかるなど、パフォーマンスに明らかな影響がある場合は、再レンダリングを防ぐ価値があります。 さらに、Reactは浅い比較を実行するため、プロップがオブジェクトまたは関数である場合でも、コンポーネントは再レンダリングされます。 ステップ3で、小道具としての関数の処理について詳しく説明します。

このステップでは、計算に時間がかかり、時間がかかるアプリケーションを作成しました。 親の再レンダリングによって子コンポーネントが再レンダリングされる方法と、を使用して再レンダリングを防止する方法を学習しました。 memo. 次のステップでは、コンポーネント内のアクションをメモ化して、特定のプロパティが変更されたときにのみアクションを実行するようにします。

ステップ2—高価なデータ計算をキャッシュする useMemo

このステップでは、低速データ計算の結果を次のように保存します。 useMemo 針。 次に、 useMemo 既存のコンポーネントに接続し、データの再計算の条件を設定します。 この手順を完了すると、高価な関数をキャッシュして、特定のデータが変更された場合にのみ実行されるようになります。

前のステップでは、コンポーネントの切り替えられた説明は親の一部でした。 ただし、代わりにそれをに追加することができます CharacterMap コンポーネント自体。 あなたがそうするとき、 CharacterMap 2つのプロパティがあります。 textshowExplanation、、次の場合に説明が表示されます showExplanation 真実です。

開始するには、 CharacterMap.js:

  1. nano src/components/CharacterMap/CharacterMap.js

の中に CharacterMap、の新しいプロパティを追加します showExplanation. の値が showExplanation 真実です:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { memo } from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  ...
}

function CharacterMap({ showExplanation, text }) {
  return(
    <div>
      {showExplanation &&
        <p>
          This display a list of the most common characters.
        </p>
      }
      Character Map:
      {itemize(text).map(character => (
        <div key={character[0]}>
          {character[0]}: {character[1]}
        </div>
      ))}
    </div>
  )
}

CharacterMap.propTypes = {
  showExplanation: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default memo(CharacterMap);

ファイルを保存して閉じます。

次に、開く App.js:

  1. nano src/components/App/App.js

説明の段落を削除して合格 showExplanation の小道具として CharacterMap:

パフォーマンス-チュートリアル/src/components/App/App.js
import React, { useReducer, useState } from 'react';
import './App.css';

import CharacterMap from '../CharacterMap/CharacterMap';

function App() {
  const [text, setText] = useState('');
  const [showExplanation, toggleExplanation] = useReducer(state => !state, false)

  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Your Text</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
          onChange={event => setText(event.target.value)}
        >
        </textarea>
      </label>
      <div>
        <button onClick={toggleExplanation}>Show Explanation</button>
      </div>
      <CharacterMap showExplanation={showExplanation} text={text} />
    </div>
  )
}

export default App;

ファイルを保存して閉じます。 これを行うと、ブラウザが更新されます。 説明を切り替えると、再び遅延が発生します。

プロファイラーを見ると、コンポーネントが再レンダリングされたことがわかります。 showExplanation 小道具が変更されました:

The memo 関数は小道具を比較し、小道具が変更されていない場合は再レンダリングを防ぎますが、この場合は showExplanation propは変更されるため、コンポーネント全体が再レンダリングされ、コンポーネントが再実行されます。 itemize 関数。

この場合、コンポーネント全体ではなく、コンポーネントの特定の部分をメモ化する必要があります。 Reactはと呼ばれる特別なフックを提供します useMemo これを使用して、再レンダリング間でコンポーネントの一部を保持できます。 フックは2つの引数を取ります。 最初の引数は、メモ化する値を返す関数です。 2番目の引数は、依存関係の配列です。 依存関係が変更された場合、 useMemo 関数を再実行し、値を返します。

実装する useMemo、最初に開く CharacterMap.js:

  1. nano src/components/CharacterMap/CharacterMap.js

と呼ばれる新しい変数を宣言します characters. 次に電話 useMemo そして、の値を返す無名関数を渡します itemize(text) 最初の引数とを含む配列として text 2番目の引数として。 いつ useMemo 実行すると、次の結果が返されます itemize(text)characters 変数。

の呼び出しを置き換えます itemize JSXで characters:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { memo, useMemo } from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  ...
}

function CharacterMap({ showExplanation, text }) {
  const characters = useMemo(() => itemize(text), [text]);
  return(
    <div>
      {showExplanation &&
        <p>
          This display a list of the most common characters.
        </p>
      }
      Character Map:
      {characters.map(character => (
        <div key={character[0]}>
          {character[0]}: {character[1]}
        </div>
      ))}
    </div>
  )
}

CharacterMap.propTypes = {
  showExplanation: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default memo(CharacterMap);

ファイルを保存します。 これを行うと、ブラウザがリロードされ、説明を切り替えても遅延は発生しません。

コンポーネントのプロファイルを作成すると、コンポーネントが再レンダリングされることがわかりますが、レンダリングにかかる時間ははるかに短くなります。 この例では、.7ミリ秒かかりましたが、 useMemo 針。 これは、Reactがコンポーネントを再レンダリングしているが、に含まれている関数を再実行していないためです。 useMemo 針。 コンポーネントの他の部分を更新できるようにしながら、結果を保持することができます。

テキストボックス内のテキストを変更した場合でも、依存関係のために遅延が発生します—text-変更されたので useMemo 関数を再実行します。 再実行されなかった場合は、古いデータがあります。 重要な点は、必要なデータが変更された場合にのみ実行されるということです。

このステップでは、コンポーネントの一部をメモ化しました。 高価な関数を残りのコンポーネントから分離し、 useMemo 特定の依存関係が変更された場合にのみ関数を実行するようにフックします。 次のステップでは、浅い比較の再レンダリングを防ぐために関数をメモします。

ステップ3—機能の同等性チェックを管理する useCallback

このステップでは、JavaScriptで比較するのが難しい小道具を処理します。 小道具が変更されると、Reactは厳密な同等性チェックを使用します。 このチェックにより、フックを再実行するタイミングとコンポーネントを再レンダリングするタイミングが決まります。 JavaScriptの関数とオブジェクトを比較するのは難しいため、小道具は事実上同じであるにもかかわらず、再レンダリングをトリガーする状況があります。

あなたは使用することができます useCallback 再レンダリング間で関数を保持するためのフック。 これにより、親コンポーネントが関数を再作成するときに不要な再レンダリングが防止されます。 このステップの終わりまでに、を使用して再レンダリングを防ぐことができるようになります useCallback 針。

あなたがあなたを構築するとき CharacterMap コンポーネントの場合、より柔軟にする必要がある場合があります。 の中に itemize 関数では、常に文字を小文字に変換しますが、コンポーネントの一部のコンシューマーはその機能を望まない場合があります。 大文字と小文字を比較したり、すべての文字を大文字に変換したりする場合があります。

これを容易にするために、と呼ばれる新しい小道具を追加します transformer それはキャラクターを変えるでしょう。 The transformer 関数は、引数として文字を取り、ある種の文字列を返すものになります。

の中に CharacterMap、 追加 transformer 小道具として。 それを与える PropType デフォルトの関数の null:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { memo, useMemo } from 'react';
import PropTypes from 'prop-types';

function itemize(text){
  const letters = text.split('')
    .filter(l => l !== ' ')
    .reduce((collection, item) => {
      const letter = item.toLowerCase();
      return {
        ...collection,
        [letter]: (collection[letter] || 0) + 1
      }
    }, {})
  return Object.entries(letters)
    .sort((a, b) => b[1] - a[1]);
}

function CharacterMap({ showExplanation, text, transformer }) {
  const characters = useMemo(() => itemize(text), [text]);
  return(
    <div>
      {showExplanation &&
        <p>
          This display a list of the most common characters.
        </p>
      }
      Character Map:
      {characters.map(character => (
        <div key={character[0]}>
          {character[0]}: {character[1]}
        </div>
      ))}
    </div>
  )
}

CharacterMap.propTypes = {
  showExplanation: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired,
  transformer: PropTypes.func
}

CharacterMap.defaultProps = {
  transformer: null
}

export default memo(CharacterMap);

次に、更新します itemize 取る transformer 引数として。 交換してください .toLowerCase 変圧器による方法。 もしも transformer 真実です、関数を呼び出します item 引数として。 それ以外の場合は、 item:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { memo, useMemo } from 'react';
import PropTypes from 'prop-types';

function itemize(text, transformer){
  const letters = text.split('')
    .filter(l => l !== ' ')
    .reduce((collection, item) => {
      const letter = transformer ? transformer(item) : item;
      return {
        ...collection,
        [letter]: (collection[letter] || 0) + 1
      }
    }, {})
  return Object.entries(letters)
    .sort((a, b) => b[1] - a[1]);
}

function CharacterMap({ showExplanation, text, transformer }) {
    ...
}

CharacterMap.propTypes = {
  showExplanation: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired,
  transformer: PropTypes.func
}

CharacterMap.defaultProps = {
  transformer: null
}

export default memo(CharacterMap);

最後に、 useMemo 針。 追加 transformer 依存関係として、それをに渡します itemize 関数。 依存関係が網羅的であることを確認する必要があります。 つまり、依存関係として変更される可能性のあるものをすべて追加する必要があります。 ユーザーが変更した場合 transformer 異なるオプションを切り替えることにより、正しい値を取得するために関数を再実行する必要があります。

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { memo, useMemo } from 'react';
import PropTypes from 'prop-types';

function itemize(text, transformer){
  ...
}

function CharacterMap({ showExplanation, text, transformer }) {
  const characters = useMemo(() => itemize(text, transformer), [text, transformer]);
  return(
    <div>
      {showExplanation &&
        <p>
          This display a list of the most common characters.
        </p>
      }
      Character Map:
      {characters.map(character => (
        <div key={character[0]}>
          {character[0]}: {character[1]}
        </div>
      ))}
    </div>
  )
}

CharacterMap.propTypes = {
  showExplanation: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired,
  transformer: PropTypes.func
}

CharacterMap.defaultProps = {
  transformer: null
}

export default memo(CharacterMap);

ファイルを保存して閉じます。

このアプリケーションでは、ユーザーが異なる機能を切り替えることができるようにしたくありません。 ただし、文字は小文字にする必要があります。 定義する transformerApp.js これにより、文字が小文字に変換されます。 この関数は変更されませんが、に渡す必要があります CharacterMap.

開ける App.js:

  1. nano src/components/App/App.js

次に、と呼ばれる関数を定義します transformer 文字を小文字に変換します。 関数を小道具としてに渡す CharacterMap:

パフォーマンス-チュートリアル/src/components/CharacterMap/CharacterMap.js
import React, { useReducer, useState } from 'react';
import './App.css';

import CharacterMap from '../CharacterMap/CharacterMap';

function App() {
  const [text, setText] = useState('');
  const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
  const transformer = item => item.toLowerCase();

  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Your Text</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
          onChange={event => setText(event.target.value)}
        >
        </textarea>
      </label>
      <div>
        <button onClick={toggleExplanation}>Show Explanation</button>
      </div>
      <CharacterMap showExplanation={showExplanation} text={text} transformer={transformer} />
    </div>
  )
}

export default App;

ファイルを保存します。 そうすると、説明を切り替えると遅延が戻っていることがわかります。

コンポーネントのプロファイルを作成すると、小道具が変更され、フックが変更されたため、コンポーネントが再レンダリングされることがわかります。

よく見ると、 showExplanation 小道具が変更されました。ボタンをクリックしたので意味がありますが、 transformer 小道具も変更されました。

で状態を変更したとき App ボタンをクリックすると、 App コンポーネントが再レンダリングされ、再宣言されました transformer. 関数は同じですが、前の関数と参照的に同一ではありません。 つまり、前の関数と厳密には同じではありません。

次のコードブロックに示すように、ブラウザコンソールを開いて同じ関数を比較すると、比較が偽であることがわかります。

const a = () = {};
const b = () = {};

a === a
// This will evaluate to true

a === b
// This will evaluate to false

を使用して === 比較演算子の場合、このコードは、2つの関数が同じ値であっても、等しいとは見なされないことを示しています。

この問題を回避するために、Reactはと呼ばれるフックを提供します useCallback. フックはに似ています useMemo:最初の引数として関数を取り、2番目の引数として依存関係の配列を取ります。 違いは useCallback 関数の結果ではなく、関数を返します。 以下のような useMemo フック、依存関係が変更されない限り、関数は再作成されません。 つまり、 useMemo フックイン CharacterMap.js 同じ値を比較し、フックは再実行されません。

の中に App.js、 輸入 useCallback 最初の引数として無名関数を渡し、2番目の引数として空の配列を渡します。 あなたは決して望んでいない App この関数を再作成するには:

パフォーマンス-チュートリアル/src/components/App/App.js
import React, { useCallback, useReducer, useState } from 'react';
import './App.css';

import CharacterMap from '../CharacterMap/CharacterMap';

function App() {
  const [text, setText] = useState('');
  const [showExplanation, toggleExplanation] = useReducer(state => !state, false)
  const transformer = useCallback(item => item.toLowerCase(), []);

  return(
    <div className="wrapper">
      <label htmlFor="text">
        <p>Your Text</p>
        <textarea
          id="text"
          name="text"
          rows="10"
          cols="100"
          onChange={event => setText(event.target.value)}
        >
        </textarea>
      </label>
      <div>
        <button onClick={toggleExplanation}>Show Explanation</button>
      </div>
      <CharacterMap showExplanation={showExplanation} text={text} transformer={transformer} />
    </div>
  )
}

export default App;

ファイルを保存して閉じます。 そうすると、関数を再実行せずに説明を切り替えることができます。

コンポーネントのプロファイルを作成すると、フックが実行されなくなっていることがわかります。

この特定のコンポーネントでは、実際には必要ありません useCallback 針。 コンポーネントの外部で関数を宣言することができ、それが再レンダリングされることはありません。 ある種のpropまたはステートフルデータが必要な場合にのみ、コンポーネント内で関数を宣言する必要があります。 ただし、内部状態や小道具に基づいて関数を作成する必要がある場合があり、そのような状況では、 useCallback 再レンダリングを最小限に抑えるためのフック。

このステップでは、を使用して再レンダリング間で関数を保持しました useCallback 針。 また、フックの小道具や依存関係と比較した場合に、これらの関数がどのように同等性を維持するかについても学びました。

結論

これで、高価なコンポーネントのパフォーマンスを向上させるためのツールが手に入りました。 使用できます memo, useMemo、 と useCallback コストのかかるコンポーネントの再レンダリングを回避します。 しかし、これらすべての戦略には、独自のパフォーマンスコストが含まれています。 memo プロパティを比較するために追加の作業が必要になり、フックは各レンダリングで追加の比較を実行する必要があります。 これらのツールは、プロジェクトに明確なニーズがある場合にのみ使用してください。そうしないと、独自のレイテンシが追加されるリスクがあります。

最後に、すべてのパフォーマンスの問題に技術的な修正が必要なわけではありません。 APIの速度が遅い、データ変換が大きいなど、パフォーマンスコストが避けられない場合があります。そのような状況では、読み込みコンポーネントをレンダリングするか、非同期関数の実行中にプレースホルダーを表示するか、その他のユーザー向けの機能強化を行うことで、デザインを使用して問題を解決できます。経験。

Reactチュートリアルをもっと読みたい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。