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

推薦する

Nodejs モジュール システムのソースコード分析

目次概要CommonJS 仕様Node の CommonJS 仕様の実装モジュールのエクスポートとイ...

XHTML 3つの文書型宣言

XHTML は 3 つのドキュメント タイプ宣言を定義します。最もよく使用されるのは XHTML T...

デザインリファレンス 美しく独創的なブログデザイン

以下にリストされているすべてのブログはオリジナルであり、独自にデザインされています。これらは、他者が...

Windows 10 での MySQL 8.0.22 のインストールと設定方法のグラフィック チュートリアル

MySQL 8.0.22のインストールと設定方法のグラフィックチュートリアル、参考までに、具体的な内...

nginx で仮想ホストを構成するための詳細な手順

仮想ホストは、インターネット上で実行されているサーバー ホストを複数の「仮想」ホストに分割する特殊な...

VueのVuexの4つの補助機能について

目次1. 補助機能2. 例1. mapState と mapGetters 2. mapMutati...

MySQL CHARとVARCHARの保存と読み取りの違い

導入保存時と読み取り時に CHAR 型と VARCHAR 型の違いを本当にご存知ですか?まずいくつか...

Net Core実装プロセス分析のDoc​​kerインストールと展開

1. Dockerのインストールと設定 #CentOS をインストールし、Docker パッケージを...

HTMLタグIDは変数にできる

<table id=" <%=var1%>">、var1...

表示しているページのスナップショットを Baidu が保存できないように設定する方法

今日、Baidu でページを検索したところ、ページが削除されていたため、当然 Baidu スナップシ...

Docker コンテナで DockerFile を使用して複数の Tomcat サービスをデプロイする手順

1. [admin@JD ~]$ cd opt #ルートディレクトリにoptと入力2. [admin...

位置固定オフセット問題を解決する方法の詳細な説明

質問CSS 固定配置の position:fixed は非常に使いやすいです。ブラウザのビューポート...

jQueryチェーン呼び出しの詳細な説明

目次チェーン呼び出し小さなケースチェーン呼び出しjQuery オブジェクトが任意のメソッド (ノード...

CentOS 6.5 i386 インストール MySQL 5.7.18 詳細チュートリアル

ほとんどの人はMySQLをコンパイルしてシステムディレクトリに置きますが、私のやり方はコンパイルした...

Navicatは機能ソリューション共有を作成できません

初めて MySQL FUNCTION を書いたとき、エラーが何度も発生しました。 Err] 1064...