React Hooksを使用する際のよくある落とし穴

React Hooksを使用する際のよくある落とし穴

React Hooks は React 16.8 で導入された新しい機能で、クラスを使用せずに状態やその他の機能を使用できるようになります。 React Hooks が解決しようとしている問題は、状態の共有です。これは、レンダリング プロパティと高階コンポーネントに続く 3 番目の状態ロジック再利用ソリューションであり、JSX ネスト地獄の問題を引き起こしません。

なぜフックなのか?

Hooks を紹介する前に、まずは React のコンポーネント作成方法についてお話ししたいと思います。1 つはクラス コンポーネント、もう 1 つは純粋な関数コンポーネントです。React チームは、コンポーネントが複雑なコンテナになるのではなく、データ フローのパイプラインにすぎないことを望んでいます。開発者は必要に応じてパイプラインを組み合わせることができます。つまり、コンポーネントはクラスではなく関数として記述するのが最適です。 。

関数コンポーネントは、クラスコンポーネントよりもビジネスロジックコードの分離やコンポーネントの再利用に便利です。また、関数コンポーネントはクラスコンポーネントよりも軽量です。React Hooks 以前は、関数コンポーネントは LocalState を実装できなかったため、関数コンポーネントを使用して localstate を持つコンポーネントを記述することはできませんでした。これにより、関数コンポーネントの適用範囲が制限されていましたが、React Hooks によって関数コンポーネントの機能が拡張されました。ただし、使用中は次の問題にも注意する必要があります。そうしないと、落とし穴に陥り、パフォーマンスが低下します。これらの罠を回避するには、以下の手順に従ってください。

1. コンポーネント関数の外部にある状態変化に関係のない変数とメソッドを抽出する

状態が変化するたびに、関数コンポーネント全体が再実行されます。その結果、関数コンポーネント内で定義されたメソッドと変数が再作成され、それらにメモリが再割り当てされるため、パフォーマンスに影響します。

React をインポートし、{useState、useCallback} を "react" からインポートします。

// 状態が変化するたびにメソッドがメモリを再割り当てすることをテストします。let testFooMemoAlloc = new Set();

const Page = (props:any) => {
  console.log('状態が変化するたびに、関数コンポーネントは最初から実行されます')
  定数[count, setCount] = useState(0);
  定数calc = () => {
    setCount(カウント + 1);
  }

  定数バー = {
    a:1、
    b:2、
    c: 「状態に依存しない変数定義」
  }
 
  定数doFoo = () => {
    console.log('状態に関係のないメソッド');

  }
  testFooMemoAlloc.add(doFoo)
  
  戻る (
    <>
      <button onClick={calc}>1 を追加</button>
      <p>カウント:{count}</p>
      <p>testFooMemoAlloc.size が増加する場合、メモリが毎回再割り当てされることを意味します: {testFooMemoAlloc.size}</p>
    </>
  )
}

デフォルトページをエクスポートします。 

状態の変更に関連する変数とメソッドはフック コンポーネントに配置する必要がありますが、状態に関連しない変数とメソッドは関数コンポーネントの外部に抽出して、状態が更新されるたびにメモリが再割り当てされるのを防ぐことができます。また、useMemo と useCallback を使用してそれぞれ変数と関数をラップし、同じ効果を実現することもできます。これについては後で説明します。

React をインポートし、{useState、useCallback} を "react" からインポートします。

// 状態が変化するたびにメソッドがメモリを再割り当てすることをテストします。let testFooMemoAlloc = new Set();

定数バー = {
  a:1、
  b:2、
  c: 「状態に依存しない変数定義」
}

定数doFoo = () => {
  console.log('状態に関係のないメソッド');

}

const Page = (props:any) => {
  console.log('状態が変化するたびに、関数コンポーネントは最初から実行されます')
  定数[count, setCount] = useState(0);
  定数calc = () => {
    setCount(カウント + 1);
  }

  testFooMemoAlloc.add(doFoo)
  
  戻る (
    <>
      <button onClick={calc}>1 を追加</button>
      <p>カウント:{count}
      <p>testFooMemoAlloc.size が増加する場合、メモリが毎回再割り当てされることを意味します: {testFooMemoAlloc.size}</p>
    </>
  )
}

デフォルトページをエクスポートします。 

2. サブコンポーネントをメモでパッケージ化する

親コンポーネントによる子コンポーネントの導入により、不要なレンダリングが繰り返されることになります。親コンポーネントがカウントを更新するたびに、子コンポーネントも更新されます。

React をインポートし、{useState} を "react" から取得します。
const 子 = (props:any) => {
    console.log('サブコンポーネント?')
    戻る(
        <div>私は子コンポーネントです</div>
    );
}
const Page = (props:any) => {
    定数[count, setCount] = useState(0);
    戻る (
        <>
            <button onClick={(e) => { setCount(count+1) }}>1 追加</button>
            <p>カウント:{count}
            <子供 />
        </>
    )
}

デフォルトページをエクスポートします。 

メモを使用すると、カウント変更サブコンポーネントは更新されません

「react」から React,{useState,memo} をインポートします。
const 子 = memo((props:any) => {
    console.log('サブコンポーネント?')
    戻る(
        <div>私は子コンポーネントです</div>
    );
})
const Page = (props:any) => {
    定数[count, setCount] = useState(0);
    戻る (
        <>
            <button onClick={(e) => { setCount(count+1) }}>1 追加</button>
            <p>カウント:{count}
            <子供 />
        </>
    )
}

デフォルトページをエクスポートします。 

2 番目のパラメータをメモに渡すと、オブジェクトの詳細な比較が可能になります。子コンポーネントによって渡されるプロパティ値が変更されない場合、子コンポーネントは意味のないレンダリングを実行しません。

memo は関数コンポーネントだけでなく、クラスコンポーネントにも適用できます。これは高階コンポーネントです。デフォルトでは、複雑なオブジェクトに対して浅い比較のみを実行します。深い比較を行う場合は、2 番目のパラメータを渡すことができます。 shouldComponentUpdateとは異なり、 deepCompare がtrueを返すと render はトリガーされませんが、 falseを返すと render はトリガーされます。そして、 shouldComponentUpdate正反対です。

「react」から React、{useState、memo} をインポートします。
「./deepCompare」から deepCompare をインポートします。

const 子 = memo((props:any) => {
    console.log('サブコンポーネント')
  戻る (
      <>
      <div>私は子コンポーネントです</div>
      <div>{ props.fooObj.a } </div>
      </>
    );
}, ディープ比較)

const Page = (props:any) => {
  定数[count, setCount] = useState(0);
  const [fooObj, setFooObj] = useState({ a: 1, b: { c: 2 } })
  console.log('ページのレンダリングが開始されます')
  定数calc = () => {
    setCount(カウント + 1);
    (カウント === 3)の場合{
      setFooObj({ b: { c: 2 }, a: count })
    }
  }
  定数doBar = () => {
    console.log('メソッドを子コンポーネントに渡して、不要なレンダリングが発生するかどうかをテストします')
  }
    戻る (
        <>
        <button onClick={calc}>1 を追加</button>
        <p>カウント:{count}
        <子 fooObj={fooObj} doBar={doBar} />
        </>
    )
}

デフォルトページをエクスポートします。
// 2 つのオブジェクトが等しいかどうかを深く比較します export default function deepCompare(prevProps: any, nextProps: any) {
  const len: 数値 = 引数.長さ;
  leftChain: any[] = []; とします。
  rightChain: any = []; とします。
  // // console.log({ 引数 });
  //
  長さが2以下の場合
    // console.log('2 つのオブジェクトのプロパティを比較するには、2 つのオブジェクトを渡す必要があります');
    true を返します。
  }
  // (i = 1; i < len; i++ の場合) {
  // 左チェーン = [];
  // 右チェーン = [];
  console.log({ 前のプロパティ、次のプロパティ });
  if (!compare2Objects(前のProps、次のProps、左チェーン、右チェーン)) {
    // console.log('2つのオブジェクトは等しくありません');
    false を返します。
  }
  // }
  // console.log('2つのオブジェクトは等しい');

  true を返します。
}

関数 compare2Objects(前のプロパティ: 任意、次のプロパティ: 任意、左チェーン: 任意、右チェーン: 任意) {
  var p;

  // 両方の値が NaN の場合、JS では等しくありませんが、ここでは等しいと見なすのが妥当です if (isNaN(prevProps) && isNaN(nextProps) && typeof prevProps === 'number' && typeof nextProps === 'number') {
    true を返します。
  }

  // 元の値の比較 if (prevProps === nextProps) {
    console.log('元の値', prevProps, nextProps);
    true を返します。
  }

  //型の比較を構築if (
    (typeof prevProps === 'function' && typeof nextProps === 'function') ||
    (前のProps インスタンス Date && 次のProps インスタンス Date) ||
    (前のプロパティのインスタンス RegExp && 次のプロパティのインスタンス RegExp) ||
    (前のProps インスタンス String && 次のProps インスタンス String) ||
    (前のProps インスタンス オブ ナンバー && 次のProps インスタンス オブ ナンバー)
  ){
    console.log('function', prevProps.toString() === nextProps.toString());
    prevProps.toString() === nextProps.toString() を返します。
  }

  // 2つの比較変数の値がnullかつ未定義の場合、ここで終了します if (!(prevProps instanceof Object && nextProps instanceof Object)) {
    console.log(prevProps, nextProps, 'prevProps instanceof Object && nextProps instanceof Object');
    false を返します。
  }

  if (prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)) {
    console.log('prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)');
    false を返します。
  }

  // コンストラクタが等しくない場合、2つのオブジェクトは等しくありません if (prevProps.constructor !== nextProps.constructor) {
    console.log('前のProps.constructor !== 次のProps.constructor');
    false を返します。
  }

  // プロトタイプが等しくない場合、2つのオブジェクトは等しくありません if (prevProps.prototype !== nextProps.prototype) {
    console.log('前のProps.prototype !== 次のProps.prototype');
    false を返します。
  }

  左チェーンのインデックスが前のプロパティより -1 の場合 || 右チェーンのインデックスが次のプロパティより -1 の場合
    console.log('leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1');
    false を返します。
  }

  // 次のプロパティオブジェクトを走査し、等しくないケースを優先的に比較します for (p in nextProps) {
    if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
      console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
      false を返します。
    } そうでない場合 (typeof nextProps[p] !== typeof prevProps[p]) {
      console.log('typeof nextProps[p] !== typeof prevProps[p]');
      false を返します。
    }
  }
  // console.log('p in prevProps');
  // 前のプロパティオブジェクトを走査し、等しくないケースを優先的に比較します for (p in prevProps) {
    // 特定のプロパティ値が存在するかどうか if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
      console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
      false を返します。
    }
    // プロパティ値の型は等しいか else if (typeof nextProps[p] !== typeof prevProps[p]) {
      console.log('typeof nextProps[p] !== typeof prevProps[p]');
      false を返します。
    }

    console.log('typeof prevProps[p]', typeof prevProps[p]);
    スイッチ (typeof prevProps[p]) {
      // オブジェクト型と関数型の処理 case 'object':
      ケース '関数':
        leftChain.push(前のプロパティ);
        rightChain.push(次のProps);

        if (!compare2Objects(前のProps[p]、次のProps[p]、左チェーン、右チェーン)) {
          console.log('!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)');
          false を返します。
        }

        左チェーン.pop();
        右チェーン.pop();
        壊す;

      デフォルト:
        // 基本的な型処理 if (prevProps[p] !== nextProps[p]) {
          false を返します。
        }
        壊す;
    }
  }

  true を返します。
} 

3. コンポーネントメソッドをuseCallbackでラップする

親コンポーネントが子コンポーネントにメソッドを渡す場合、memo は効果がないようです。const で定義されたメソッドでも、矢印関数でも、bind で定義されたメソッドでも、子コンポーネントはそれを実行します。

React をインポートし、{useState,memo} を 'react' から取得します。
//子コンポーネントの不要なレンダリングの例 interface ChildProps {
  名前を変更します: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('通常の関数サブコンポーネント')
  戻る(
      <>
          <div>私は通常の関数サブコンポーネントです</div>
          <button onClick={changeName}>通常の関数サブコンポーネント ボタン</button>
      </>
  );
}
FunMemo は、FunChild クラスのインスタンスです。

const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('矢印関数のサブコンポーネント')
  戻る(
      <>
          <div>私は矢印関数のサブコンポーネントです</div>
          <button onClick={changeName.bind(null,'test')}>矢印関数サブコンポーネント ボタン</button>
      </>
  );
}
定数 ArrowMemo = memo(ArrowChild);

const BindChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('バインド関数サブコンポーネント')
  戻る(
      <>
          <div>私はBind関数のサブコンポーネントです</div>
          <button onClick={changeName}>バインド関数サブコンポーネント ボタン</button>
      </>
  );
}
const BindMemo = memo(BindChild);

const Page = (props:any) => {
  定数[count, setCount] = useState(0);
  const name = "テスト";

  定数changeName = 関数() {
    console.log('サブコンポーネントに渡されるメソッドをテストします。useCallback を使用した後も、サブコンポーネントは無効にレンダリングされますか?');
  }

  戻る (
      <>
          <button onClick={(e) => { setCount(count+1) }}>1 追加</button>
          <p>カウント:{count}
          <ArrowMemo changeName={()=>changeName()}/>
          <BindMemo changeName={changeName.bind(null)}/>
          <FunMemo changeName={changeName} />
      </>
  )
}

デフォルトページをエクスポートします。 

useCallbackをパラメータ[]とともに使用し、ページが最初にレンダリングされた後にcountの値を変更すると、通常の関数を渡すサブコンポーネントはレンダリングされなくなりますが、矢印関数とbindで記述されたメソッドを渡すサブコンポーネントは引き続きレンダリングされます。

React をインポートし、{useState、memo、useCallback} を 'react' からインポートします。
//子コンポーネントの不要なレンダリングの例 interface ChildProps {
  名前を変更します: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('通常の関数サブコンポーネント')
  戻る(
      <>
          <div>私は通常の関数サブコンポーネントです</div>
          <button onClick={changeName}>通常の関数サブコンポーネント ボタン</button>
      </>
  );
}
FunMemo は、FunChild クラスのインスタンスです。

const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('矢印関数のサブコンポーネント')
  戻る(
      <>
          <div>私は矢印関数のサブコンポーネントです</div>
          <button onClick={changeName.bind(null,'test')}>矢印関数サブコンポーネント ボタン</button>
      </>
  );
}
定数 ArrowMemo = memo(ArrowChild);

const BindChild = ({ changeName}: ChildProps): JSX.Element => {
  console.log('バインド関数サブコンポーネント')
  戻る(
      <>
          <div>私はBind関数のサブコンポーネントです</div>
          <button onClick={changeName}>バインド関数サブコンポーネント ボタン</button>
      </>
  );
}
const BindMemo = memo(BindChild);

const Page = (props:any) => {
  定数[count, setCount] = useState(0);
  const name = "テスト";

  定数changeName = useCallback(() => {
    console.log('サブコンポーネントに渡されるメソッドをテストします。useCallback を使用した後も、サブコンポーネントは無効にレンダリングされますか?');
  },[])

  戻る (
      <>
          <button onClick={(e) => { setCount(count+1) }}>1 追加</button>
          <p>カウント:{count}
          <ArrowMemo changeName={()=>changeName()}/>
          <BindMemo changeName={changeName.bind(null)}/>
          <FunMemo changeName={changeName} />
      </>
  )
}

デフォルトページをエクスポートします。 

4. useMemoを使用してコンポーネント内のオブジェクト変数をラップする

子コンポーネントが memo と useCallback を使用すると、子コンポーネントにオブジェクト プロパティが渡されます。オブジェクトの値とメソッドが変更されていない場合、親コンポーネントの状態の変化に関係なく、子コンポーネントは再レンダリングされます。

React をインポートし、{useState、memo、useCallback} を 'react' からインポートします。
//子コンポーネントの不要なレンダリングの例 - memo および useCallback を使用するときに子コンポーネントにオブジェクト プロパティ値を渡す interface ChildProps {
  子スタイル: { 色: 文字列; フォントサイズ: 文字列;};
  名前を変更します: ()=>void;
}
const FunChild = ({childStyle,changeName}: ChildProps): JSX.Element => {
  console.log('通常の関数サブコンポーネント')
  戻る(
      <>
          <div style={childStyle}>私は通常の関数の子コンポーネントです</div>
          <button onClick={changeName}>通常の関数サブコンポーネント ボタン</button>
      </>
  );
}
FunMemo は、FunChild クラスのインスタンスです。

const Page = (props:any) => {
  定数[count, setCount] = useState(0);
  定数childStyle = {色:'緑'、フォントサイズ:'16px'};

  定数changeName = useCallback(() => {
    console.log('サブコンポーネントに渡されるメソッドをテストします。useCallback を使用した後も、サブコンポーネントは無効にレンダリングされますか?');
  },[])

  戻る (
      <>
          <button onClick={(e) => { setCount(count+1) }}>1 追加</button>
          <p>カウント:{count}
          <FunMemo childStyle={childStyle} changeName={changeName} />
      </>
  )
}

デフォルトページをエクスポートします。 

useMemo を使用すると、オブジェクトのプロパティを子コンポーネントに渡すときに不要な更新が発生する問題を解決できます。

'react' から React、{useState、memo、useMemo、useCallback} をインポートします。
//子コンポーネントの不要なレンダリングの例 interface ChildProps {
  子スタイル: { 色: 文字列; フォントサイズ: 文字列;};
  名前を変更します: ()=>void;
}
const FunChild = ({childStyle,changeName}: ChildProps): JSX.Element => {
  console.log('通常の関数サブコンポーネント')
  戻る(
      <>
          <div style={childStyle}>私は通常の関数の子コンポーネントです</div>
          <button onClick={changeName}>通常の関数サブコンポーネント ボタン</button>
      </>
  );
}
FunMemo は、FunChild クラスのインスタンスです。

const Page = (props:any) => {
  定数[count, setCount] = useState(0);
  const [名前、setName] = useState("");
  定数childStyle = {色:'緑'、フォントサイズ:'16px'};

  定数changeName = useCallback(() => {
    setName('名前を変更する')
  }, [])
  定数childStyleMemo = useMemo(() => {
    戻る {
      color: name === '名前を変更する' ? 'red':'green',
      フォントサイズ: '16px'
    }
  }、 [名前])

  戻る (
      <>
          <button onClick={(e) => { setCount(count+1) }}>1 追加</button>
          <p>カウント:{count}
          <FunMemo childStyle={childStyleMemo} changeName={changeName} />
      </>
  )
}

デフォルトページをエクスポートします。 

以上がReact Hooks使用回避ガイドの詳しい内容です。React Hooksの使用に関する詳しい情報は、123WORDPRESS.COM内の他の関連記事にも注目してください!

以下もご興味があるかもしれません:
  • React Hooksコンポーネント間で値を渡す方法の詳細な説明(tsを使用)
  • ReactHooks バッチ更新状態とルートパラメータの取得例の分析
  • React Hooksの詳細な説明
  • 30分でReact Hooksを包括的に理解できます
  • Reactフックの仕組み
  • Reactにおけるフックの一般的な使用法
  • React の 10 個のフックの紹介

<<:  MySQL インデックスの種類 (通常、ユニーク、フルテキスト) の説明

>>:  Docker で Portainer ビジュアル インターフェースを構築するための詳細な手順

推薦する

htm 初心者ノート(初心者は必ず読んでください)

1. HTMLとは何かHTML (ハイパーテキスト マークアップ言語): ハイパーテキスト マーク...

ウェブページで任意のフォントを使用する実践的な操作とデモ

以前、「Web ページにシステムに組み込まれていないフォントを埋め込む」という研究をしたことがありま...

MySQL サーバーの接続、切断、および cmd 操作

mysql コマンドを使用して MySQL サーバーに接続します。 MySQL サーバーが起動したら...

FileZilla を使用して FTP ファイル サービスを素早く構築する方法

ファイルの保存とアクセスを容易にするために、FTPサービスが特別に構築されています。 FTP サーバ...

いくつかのMySQL更新操作のケース分析

目次ケーススタディアカウント残高を更新する直接更新楽観的ロック方式ロックフリーソリューションキューイ...

MySQL主キー命名戦略関連

最近、データライフサイクル管理の詳細を整理していたときに、小さな問題を発見しました。それは、MySQ...

CSSプリコンパイル言語とその違いの詳細な説明

1. 何ですか マークアップ言語として、CSSは比較的シンプルな構文とユーザーに対する要件が低いが、...

Linux で gdb を使用してコア ファイルをデバッグする方法

1.コアファイルプログラム実行中にセグメンテーション エラー (コア ダンプ) が発生すると、プログ...

史上最も簡単な MySQL データのバックアップと復元のチュートリアル (パート 2) (パート 37)

データのバックアップと復元パート3の詳細は次のとおりです基本的な概念:バックアップ、現在のデータまた...

Ubuntuデュアルシステムが起動時に停止する問題の解決方法の詳細な説明

起動時に Ubuntu デュアル システムが停止する問題の解決方法 (Ubuntu 16.04 およ...

Mysql は null 値の first/last メソッドの例を実装します

序文MySQL が SQL SELECT コマンドと WHERE 句を使用してテーブルからデータを読...

CentOS8 でローカル yum ソースを構成するための詳細なチュートリアル

centos8 ディストリビューションは、BaseOS および AppStream リポジトリを通じ...

MySQL btree インデックスとハッシュ インデックスの違い

MySQL では、ほとんどのインデックス (PRIMARY KEY、UNIQUE、INDEX、FUL...

W3C チュートリアル (14): W3C RDF および OWL アクティビティ

RDF と OWL は、2 つの重要なセマンティック ウェブ テクノロジーです。 RDF と OWL...

Vue lazyload 画像遅延読み込み例の詳細な説明

ドキュメント: https://github.com/hilongjw/vue-lazyload 1...