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

推薦する

入力ボックスのオートコンプリート機能をオフにする

これで、autocomplete と呼ばれる input の属性を使用できるようになりました。オート...

ユーザーはその理由を知る必要がある

証券会社にいた頃、設計業務が忙しくなかったため、商品のマニュアルを書く役割を担ったことがありました。...

React のグローバル状態管理の 3 つの基本メカニズムの調査

目次序文小道具コンテクスト州要約する序文最新のフロントエンド フレームワークはすべて、コンポーネント...

IE6 および IE7 で DIV コンテナの固定高さを使用するためのヒント

IE6 と IE7 では CSS の解釈に多くの違いがあります。今日はそのうちの 1 つである高さに...

CentOS 7.x に ZSH ターミナルをインストールする方法

1. 基本コンポーネントをインストールするまず、 yumコマンドを実行して、コードpullために必要...

ウェブ上の模倣と盗作に関する議論

2005年に業界に入ってから数か月後、労働者の日休みの期間中、1か月以上毎日12時まで残業をしました...

MySQL alter ignore構文の詳細な説明

今日仕事中に、ビジネス側から次のような質問をされました。テーブルがあり、一意のフィールドを追加する必...

Linux 環境の Apache サーバーでセカンダリドメイン名を設定する方法の詳細な説明

この記事では、Linux 環境の Apache サーバーでセカンダリ ドメイン名を構成する方法につい...

MySQLの共通関数を使用してJSONを処理する方法

公式ドキュメント: JSON 関数名前説明JSON_APPEND() JSONドキュメントにデータを...

プロセスごとにネットワーク帯域幅を監視する Linux ツール Nethogs のインストールと展開

概要Linux 用のオープン ソース ネットワーク監視ツールは数多くあります。たとえば、帯域幅の使用...

マークアップ言語 - テキストの CSS スタイルを指定する

123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...

MySQL スロークエリログの有効化と設定

導入MySQL スロー クエリ ログは、問題のある SQL ステートメントのトラブルシューティングや...

Centos6.6 で php7 + nginx 環境をインストールする方法

この記事では、centos6.6 で php7 + nginx 環境をインストールする方法について説...

ReactプロジェクトにSCSSを導入する方法

まず依存関係をダウンロードします yarn sass-loader ノード sass を追加します次...

Docker可視化ツールPortainerの導入と中国語翻訳

#docker 検索#docker プルポーター1. イメージを取得した後、中国語パッケージをダウン...