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 ビジュアル インターフェースを構築するための詳細な手順

推薦する

Centos7 FFmpeg オーディオ/ビデオ ツールのインストールに関する簡単なドキュメント

ffmpeg は非常に強力なオーディオおよびビデオ処理ツールです。公式 Web サイトは http:...

MySQLコンテナ間のレプリケーション構成例の詳細な説明

背景先週、会社で MySQL レプリケーションのトレーニングを受けたので、今週末は学んだことを実践す...

BT Baota Panel php7.3 および php7.4 が ZipArchive をサポートしない問題の解決方法

Baota PanelのPHP7.3バージョンがZipArchiveをサポートしていないため、プログ...

5 分で vue-cli3 を使用してプロジェクトを作成する方法を説明します (初心者向けガイド)

目次1. Vue環境を構築する2. Vue スキャフォールディングツール3. プロジェクトを作成する...

Zabbix パスワードをリセットする方法 (ワンステップ)

問題の説明長い間アカウントパスワードを入力して Zabbix にログインしていないため、管理者パスワ...

JS でパブリッシュ サブスクライブ モデルを作成する

目次1. シーン紹介2 コードの最適化2.1 ファンを増やす問題を解決する2.2 作品追加の問題を解...

Tencent Cloud Server Tomcat ポートにアクセスできない場合の解決策

最近、Tencent Cloudを使用してサーバーを設定しました。使用中に、tomcatポートにアク...

MySQLの最適化の詳細な分析とパフォーマンス

導入データベースを使用したことがある人なら、機能面での like 記号と = 記号の類似点と相違点を...

MySQLは現在の日付と時刻を取得する関数の例の詳細な説明

現在の日付 + 時刻 (日付 + 時刻) を取得する関数: now() mysql> now(...

IE6 で JS エラーが発生し、CSS が適用されない HTML エンコードの問題の解決策

テストでは、ページ定義がutf-8でエンコードされている場合、 js ファイルに中国語などのマルチバ...

MySQL の自動インクリメント主キーが連続していないのはなぜですか?

目次1. はじめに2. 自己増分ストレージの説明3つの自己付加価値修正メカニズム4. 自己評価を修正...

CSS のみを使用して折りたたまれたヘッダー効果を作成する方法の例コード

折りたたまれたヘッダーは、特別オファーや重要なお知らせなど、ユーザーにとって重要な情報を表示するのに...

CentOS で新しいユーザーを作成し、キーログインを有効にする方法

目次新しいユーザーを作成する新規ユーザーを承認する新規ユーザーのSSHキーログインを有効にする他のS...

JavaScript 関数のパフォーマンスを測定するさまざまな方法の比較

目次概要パフォーマンス.nowコンソール.time時間精度を短縮注意事項分割して征服する入力値に注意...

docker-compose を使用して MySQL を実行する方法

ディレクトリ構造 。 │ .env │ docker-compose.yml │ └─mysql ├...