React Hooks の一般的な使用シナリオ (概要)

React Hooks の一般的な使用シナリオ (概要)

序文

React はバージョン v16.8 で新しい機能 React Hooks をリリースしました。私の意見では、React Hooks を使用すると、以前のクラス コンポーネントに比べて次のような利点があります。

  • コードが読みやすくなります。同じ関数のコード ロジックは元々、異なるライフサイクル関数に分割されていたため、開発者が保守や反復処理を行うことが困難でした。React Hooks を使用すると、関数コードを集約して、読みやすく保守しやすくなります。
  • コンポーネントツリーが浅くなります。元のコードでは、コンポーネントの状態を再利用したり、機能を強化したりするために HOC/レンダリングプロパティを使用することが多いため、コンポーネントツリーのレイヤー数とレンダリング数は間違いなく増加します。React Hooks では、これらの機能を強力なカスタムフックを通じて実現できます。

この記事では、React Hooks のほとんどの機能を理解し、上手に使用できるように、使用シナリオに基づいた例を紹介します。

ブログのgithubアドレスはhttps://github.com/fengshi123/blogです。

1. ステートフック

1. 基本的な使い方

関数状態(){
  定数[count, setCount] = useState(0);
  戻る (
      <div>
          <p>クリック回数は {count} 回です</p>
          <ボタンのクリック時={() => setCount(count + 1)}>
              クリックしてください
          </ボタン>
      </div>
  )
}

2. 更新

更新方法には、直接更新と機能更新の 2 種類があります。それぞれのアプリケーション シナリオの違いは次のとおりです。

  • 古い状態に依存しない値を直接更新します。
  • 機能の更新は古い状態の値に依存します。
// setState(newCount) を直接更新します。

// 機能更新 setState(prevCount => prevCount - 1);

3. 合併を実現する

クラス コンポーネントの setState メソッドとは異なり、useState は更新されたオブジェクトを自動的にマージせず、直接置き換えます。関数 setState をスプレッド演算子と組み合わせて使用​​することで、オブジェクトのマージと更新の効果を実現できます。

setState(前の状態 => {
  // Object.assignも使用できます
  {...prevState、...updatedValues} を返します。
});

4. 遅延初期化状態

initialState パラメータはコンポーネントの初期レンダリングでのみ有効になり、後続のレンダリングでは無視されます。適用シナリオは次のようになります。初期状態の作成にコストがかかる場合、たとえば複雑な計算によって取得する必要がある場合、関数を渡して計算し、関数内で初期状態を返すことができます。この関数は、最初のレンダリング時にのみ呼び出されます。

const [状態、setState] = useState(() => {
  定数初期状態 = someExpensiveComputation(props);
  初期状態を返します。
});

5. 重要なポイント

(1) クラス内のthis.setStateとは異なり、Hookは状態変数をマージするのではなく常に置き換えることで状態変数を更新します。
(2)状態置換ロジックはマージロジックではなく、関連する状態ロジックのその後の抽出に役立つため、単一の状態変数ではなく複数の状態変数を使用することが推奨される。
(3)State Hookのupdate関数が呼び出され、現在の状態が渡されると、Reactは子コンポーネントのレンダリングとエフェクトの実行をスキップします。 (React は Object.is 比較アルゴリズムを使用して状態を比較します。)

エフェクトフック

1. 基本的な使い方

関数 Effect(){
  定数[count, setCount] = useState(0);
  使用効果(() => {
    console.log(`${count} 回クリックしました`);
  });

  戻る (
      <div>
          <p>クリック回数は {count} 回です</p>
          <ボタンのクリック時={() => setCount(count + 1)}>
              クリックしてください
          </ボタン>
      </div>
  )
}

2. クリアな操作

メモリ リークを防ぐために、コンポーネントがアンインストールされる前にクリーンアップ関数が実行されます。コンポーネントが複数回レンダリングされる場合 (通常はそうなります)、次のエフェクトが実行される前に前のエフェクトがクリアされます。つまり、前のエフェクトの return 関数が最初に実行され、次にこのエフェクトの non-return 関数が実行されます。

使用効果(() => {
  サブスクリプション = props.source.subscribe();
  戻り値 () => {
    // サブスクリプションをクリアします subscription.unsubscribe();
  };
});

3. 実行期間

componentDidMount や componentDidUpdate とは異なり、useEffect でスケジュールされたエフェクトはブラウザの画面更新をブロックしないため、アプリの応答性が向上します (componentDidMount や componentDidUpdate はブラウザの画面更新をブロックします)。

4. パフォーマンスの最適化

デフォルトでは、React はブラウザが画面のレンダリングを完了するのを待つたびにエフェクトの呼び出しを遅らせます。ただし、2 回の再レンダリング間で特定の値が変更されていない場合は、useEffect の 2 番目のオプション パラメータとして配列を渡すことで、エフェクトの呼び出しをスキップするように React に指示できます。以下に示すように、2 回のレンダリング間で count 値が変更されていない場合は、2 回目のレンダリング後にエフェクトがスキップされます。

使用効果(() => {
  document.title = `${count} 回クリックしました`;
}, [count]); // countが変更された場合にのみ更新する

5.componentDidMountをシミュレートする

エフェクトを 1 回だけ (コンポーネントがマウントおよびアンマウントされたときのみ) 実行する場合は、次に示すように、2 番目のパラメーターとして空の配列 ([]) を渡すことができます。原理は、ポイント 4 のパフォーマンス最適化で説明したものと同じです。

使用効果(() => {
  .....
}, []);

6. ベストプラクティス

エフェクトの外部の関数がどのプロパティと状態を使用するかを覚えておくのは難しい場合があります。そのため、通常はエフェクト内で必要な関数を宣言する必要があります。

// 悪い、推奨されない関数 Example({ someProp }) {
  関数doSomething() {
    コンソールにログ出力します。
  }

  使用効果(() => {
    何かを実行します();
  }, []); // 🔴 これは安全ではありません(`someProp` を使用する `doSomething` を呼び出します)
}

// 良い、推奨される関数 Example({ someProp }) {
  使用効果(() => {
    関数doSomething() {
      コンソールにログ出力します。
    }

    何かを実行します();
  }, [someProp]); // ✅ 安全(このエフェクトでは `someProp` のみを使用します)
}

何らかの理由で関数をエフェクトに移動できない場合は、他にいくつかのオプションがあります。

  • その関数をコンポーネントの外部に移動してみることもできます。そうすれば、関数はどのプロパティや状態にも依存しなくなり、依存関係リストに表示される必要がなくなります。
  • 最後の手段として、関数をエフェクト依存関係として追加し、その定義を useCallback フックでラップすることができます。これにより、自身の依存関係が変更されない限り、レンダリング間で変更されないことが保証されます。

eslint-plugin-react-hooks の exhaustive-deps ルールを有効にすることをお勧めします。これにより、誤った依存関係を追加するときに警告が発行され、修復の提案が提供されます。

// 1. プラグインをインストールする npm i eslint-plugin-react-hooks --save-dev

// 2. eslint 設定 {
  「プラグイン」: [
    // ...
    「反応フック」
  ]、
  「ルール」: {
    // ...
    "react-hooks/rules-of-hooks": "エラー",
    "react-hooks/exhaustive-deps": "警告"
  }
}

7. 重要なポイント

(1) useEffectフックは、componentDidMount、componentDidUpdate、componentWillUnmountの組み合わせと考えることができます。
(2)Reactのクラスコンポーネントでは、レンダリング関数に副作用があってはなりません。一般的に言えば、ここで操作を実行するのは時期尚早であり、基本的にはReactがDOMを更新した後に操作を実行する必要があります。

3. コンテキストを使用する

複数レベルのデータ転送を処理するために使用されるメソッド。前のコンポーネント ツリーでは、クロスレベルの祖先コンポーネントが孫コンポーネントにデータを渡す場合、レイヤーごとにプロパティを渡すだけでなく、React Context API を使用してこれを行うこともできます。使用例は以下のとおりです。(1) React Context APIを使用してコンポーネントの外部にコンテキストを作成する

'react' から React をインポートします。
テーマコンテキストを React.createContext(0);
デフォルトの ThemeContext をエクスポートします。

(2)Context.Providerを使用して、子コンポーネントで共有できるContextオブジェクトを提供する

React をインポートし、{useState} を 'react' から取得します。
'./ThemeContext' から ThemeContext をインポートします。
'./ContextComponent1' から ContextComponent1 をインポートします。

関数ContextPage(){
  定数[count, setCount] = useState(1);
  戻る (
    <div className="アプリ">
      <ThemeContext.Provider 値 = {count}>
        <コンテキストコンポーネント1 />
      </テーマコンテキスト.プロバイダー>
      <ボタンのクリック時={() => setCount(count + 1)}>
              クリックしてください
      </ボタン>
    </div>
  );
}

デフォルトのContextPageをエクスポートします。

(3)useContext()フック関数はContextオブジェクトを導入し、その値を取得するために使用される。

// サブコンポーネント、サブコンポーネント内の孫コンポーネントを使用します import React from 'react';
'./ContextComponent2' から ContextComponent2 をインポートします。
関数ContextComponent(){
  戻る (
    <コンテキストコンポーネント2 />
  );
}
デフォルトのContextComponentをエクスポートします。


// 孫コンポーネント。孫コンポーネントで Context オブジェクトの値を使用します。import React, { useContext } from 'react';
'./ThemeContext' から ThemeContext をインポートします。
関数ContextComponent(){
  定数値 = useContext(ThemeContext);
  戻る (
    <div>useContext:{値}</div>
  );
}
デフォルトのContextComponentをエクスポートします。

4. リデューサーを使用する

1. 基本的な使い方

useState がより適しているシナリオ: たとえば、状態ロジックの処理が複雑で複数のサブ値が含まれている場合や、次の状態が前の状態に依存する場合など。以下に例を示します。

React をインポートし、 'react' から { useReducer } を追加します。
インターフェース状態タイプ{
  カウント: 数
}
インターフェースアクションタイプ{
  タイプ: 文字列
}
定数初期状態 = { カウント: 0 };
const リデューサー = (状態:状態タイプ、アクション:アクションタイプ) => {
  スイッチ(アクションタイプ){
    ケース '増分':
      戻り値: state.count + 1 };
    ケース '減少':
      戻り値: state.count - 1 };
    デフォルト:
      新しい Error() をスローします。
  }
};
定数UseReducer = () => {
  const [状態、ディスパッチ] = useReducer(リデューサー、初期状態);

  戻る (
    <div className="アプリ">
      <div>useReducer カウント:{state.count}</div>
      <button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer の削減</button>
      <button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer increase</button>
    </div>
  );
};

デフォルトのUseReducerをエクスポートします。

2. 状態の遅延初期化

インターフェース状態タイプ{
  カウント: 数
}
インターフェースアクションタイプ{
  タイプ: 文字列、
  ペイロード?: 番号
}
定数initCount = 0 
定数 init = (initCount:数値)=>{
  {count:initCount} を返す
}
const リデューサー = (状態:状態タイプ、アクション:アクションタイプ)=>{
  スイッチ(アクション.type){
    ケース '増分':
      {count: state.count + 1} を返します
    ケース '減少':
      {count: state.count - 1} を返します
    ケース 'リセット':
      init(action.paylod || 0) を返します
    デフォルト:
      新しい Error() をスローします。
  }
}
定数UseReducer = () => {
  const [状態、ディスパッチ] = useReducer(reducer、initCount、init)

  戻る (
    <div className="アプリ">
      <div>useReducer カウント:{state.count}</div>
      <button onClick={()=>{dispatch({type:'decrement'})}}>useReducer の Reduce</button>
      <button onClick={()=>{dispatch({type:'increment'})}}>useReducer 増加</button>
      <button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer の追加</button>
    </div>
  );
}
デフォルトのUseReducerをエクスポートします。

5. メモ

以下に示すように、親コンポーネントが再レンダリングされると、子コンポーネントのプロパティと状態が変更されていなくても、子コンポーネントも再レンダリングされます。
React をインポートします。{ memo, useState } から 'react' へ。

// 子コンポーネント const ChildComp = () => {
  console.log('ChildComp...');
  戻り値: (<div>ChildComp...</div>);
};

// 親コンポーネント const Parent = () => {
  定数[count, setCount] = useState(0);

  戻る (
    <div className="アプリ">
      <div>こんにちは世界{count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>クリックして増加</div>
      <チャイルドコンプ/>
    </div>
  );
};

デフォルトの親をエクスポートします。

改善: 上記の問題を解決するためにメモ パッケージを使用できますが、親コンポーネントが子コンポーネントにパラメータを渡さず、親コンポーネントが子コンポーネントに単純な型のパラメータ (文字列、数値、ブール値など) を渡す状況のみを解決します。複雑なプロパティが渡される場合は、useCallback (コールバック イベント) または useMemo (複雑なプロパティ) を使用する必要があります。

// 子コンポーネント const ChildComp = () => {
  console.log('ChildComp...');
  戻り値: (<div>ChildComp...</div>);
};

定数 MemoChildComp = memo(ChildComp);

6.メモを使う

次のシナリオを想定します: 親コンポーネントは、子コンポーネントを呼び出すときに、情報オブジェクト プロパティを渡します。親コンポーネント ボタンをクリックすると、コンソールにはレンダリングされる子コンポーネントに関する情報が出力されます。

React をインポートします。{ memo, useState } から 'react';

// 子コンポーネント const ChildComp = (info:{info:{name: string, age: number}}) => {
  console.log('ChildComp...');
  戻り値: (<div>ChildComp...</div>);
};

定数 MemoChildComp = memo(ChildComp);

// 親コンポーネント const Parent = () => {
  定数[count, setCount] = useState(0);
  const [名前] = useState('jack');
  定数[年齢] = useState(11);
  const info = { 名前、 年齢 };

  戻る (
    <div className="アプリ">
      <div>こんにちは世界{count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>クリックして増加</div>
      <MemoChildComp 情報 = {情報} />
    </div>
  );
};

デフォルトの親をエクスポートします。

理由の分析:

  • 親コンポーネント ボタンをクリックすると、親コンポーネントが再レンダリングされます。
  • 親コンポーネントがレンダリングされると、const info = { name, age } の行によって新しいオブジェクトが再生成され、子コンポーネントに渡される info プロパティの値が変更され、その結果、子コンポーネントが再レンダリングされます。

解決する:

オブジェクト属性をラップするには、useMemo を使用します。useMemo には 2 つのパラメータがあります。

  • 最初のパラメータは関数であり、返されるオブジェクトは同じ参照を指し、新しいオブジェクトは作成されません。
  • 2 番目のパラメータは配列です。最初のパラメータの関数は、配列内の変数が変更された場合にのみ新しいオブジェクトを返します。
'react' から React、{ memo、useMemo、useState } をインポートします。

// 子コンポーネント const ChildComp = (info:{info:{name: string, age: number}}) => {
  console.log('ChildComp...');
  戻り値: (<div>ChildComp...</div>);
};

定数 MemoChildComp = memo(ChildComp);

// 親コンポーネント const Parent = () => {
  定数[count, setCount] = useState(0);
  const [名前] = useState('jack');
  定数[年齢] = useState(11);
  
  // オブジェクトのプロパティをラップするには useMemo を使用します const info = useMemo(() => ({ name, age }), [name, age]);

  戻る (
    <div className="アプリ">
      <div>こんにちは世界{count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>クリックして増加</div>
      <MemoChildComp 情報 = {情報} />
    </div>
  );
};

デフォルトの親をエクスポートします。

7.コールバックを使用する

第 6 章の例を続けて、次に示すように、子コンポーネントにイベントを渡す必要があるとします。親コンポーネント ボタンをクリックすると、レンダリングされている子コンポーネントに関する情報がコンソールに出力され、子コンポーネントが再レンダリングされたことが示されます。

'react' から React、{ memo、useMemo、useState } をインポートします。

// 子コンポーネント const ChildComp = (props:any) => {
  console.log('ChildComp...');
  戻り値: (<div>ChildComp...</div>);
};

定数 MemoChildComp = memo(ChildComp);

// 親コンポーネント const Parent = () => {
  定数[count, setCount] = useState(0);
  const [名前] = useState('jack');
  定数[年齢] = useState(11);
  const info = useMemo(() => ({ 名前、年齢 }), [名前、年齢]);
  定数changeName = () => {
    console.log('出力名...');
  };

  戻る (
    <div className="アプリ">
      <div>こんにちは世界{count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>クリックして増加</div>
      <MemoChildComp 情報={情報} 変更名={変更名}/>
    </div>
  );
};

デフォルトの親をエクスポートします。

理由を分析します。

  • 親コンポーネント ボタンをクリックすると、親コンポーネントの count 変数の値 (親コンポーネントの状態値) が変更され、親コンポーネントが再レンダリングされます。
  • 親コンポーネントが再レンダリングされると、changeName 関数が再作成されます。つまり、子コンポーネントに渡された changeName プロパティが変更され、子コンポーネントがレンダリングされます。

解決する:
親コンポーネントの changeName メソッドを変更し、useCallback フック関数でラップします。useCallback パラメータは useMemo に似ています。

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

// 子コンポーネント const ChildComp = (props:any) => {
  console.log('ChildComp...');
  戻り値: (<div>ChildComp...</div>);
};

定数 MemoChildComp = memo(ChildComp);

// 親コンポーネント const Parent = () => {
  定数[count, setCount] = useState(0);
  const [名前] = useState('jack');
  定数[年齢] = useState(11);
  const info = useMemo(() => ({ name, age }), [name, age]);
  定数changeName = useCallback(() => {
    console.log('出力名...');
  }, []);

  戻る (
    <div className="アプリ">
      <div>こんにちは世界{count}</div>
      <div onClick={() => { setCount(count => count + 1); }}>クリックして増加</div>
      <MemoChildComp 情報={情報} 変更名={変更名}/>
    </div>
  );
};

デフォルトの親をエクスポートします。

8. 使用Ref

useRef の使用シナリオは次の 2 つです。

1. DOM要素を指す

以下に示すように、useRef を使用して作成された変数は入力要素を指し、ページがレンダリングされた後に入力にフォーカスします。

'react' から React をインポートします。{useRef、useEffect}。
定数 Page1 = () => {
  const myRef = useRef<HTMLInputElement>(null);
  使用効果(() => {
    myRef?.current?.focus();
  });
  戻る (
    <div>
      <span>使用参照:</span>
      <input ref={myRef} タイプ="text"/>
    </div>
  );
};

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

2. 変数の保存

公式ウェブサイトが言うように、React Hook での useRef の役割は変数のようなもので、これと似たもので、ボックスのようなもので、何でも保存できます。createRef はレンダリングするたびに新しい参照を返しますが、useRef は毎回同じ参照を返します。次の例をご覧ください。

'react' から React、{useRef、useEffect、useState } をインポートします。
定数 Page1 = () => {
    定数 myRef2 = useRef(0);
    定数[count, setCount] = useState(0)
    使用効果(()=>{
      myRef2.current = カウント;
    });
    関数handleClick(){
      タイムアウトを設定します(()=>{
        console.log(カウント); // 3
        コンソールログ(myRef2.current); // 6
      },3000)
    }
    戻る (
    <div>
      <div onClick={()=> setCount(count+1)}>クリック数</div>
      <div onClick={()=> handleClick()}>表示</div>
    </div>
    );
}

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

9. 命令型ハンドルを使用する

使用シナリオ: DOM ノード全体は ref を通じて取得され、useImperativeHandle を使用して、DOM ノード全体ではなく一部のメソッドとプロパティのみの公開を制御できます。

10.レイアウト効果を使用する

その関数シグネチャは useEffect と同じですが、すべての DOM の変更後に同期的にエフェクトを呼び出すため、ここでは示されていません。

useLayoutEffect は、通常記述する Class コンポーネントの componentDidMount および componentDidUpdate と同時に実行されます。
useEffect は、更新が完了した後、つまりポイント 1 のメソッドが実行された後に別のタスク スケジューリングを開始し、次のタスク スケジューリングで useEffect を実行します。

要約する

この記事では、使用シナリオに基づいた例を示し、React Hooks のほとんどの機能を理解して上手に使用できるようにします。

これで、React Hooksの一般的なシナリオの使用に関する記事(要約)は終了です。React Hooksの一般的なシナリオに関連するその他のコンテンツについては、123WORDPRESS.COMの以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも123WORDPRESS.COMをよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Reactフックの長所と短所
  • フックを使用して React コンポーネントを書くときに注意すべき 5 つの点
  • Reactフック入門チュートリアル
  • Reactフックとzarmコンポーネントライブラリ構成に基づいてh5フォームページを開発するためのサンプルコード
  • Reactはフックを使用して、制御されたコンポーネントの状態バインディングを簡素化します。
  • 今年最もエキサイティングな React の新機能、React Hooks を 30 分でマスターする
  • 完全なReact Hooksの練習を記録する
  • React Hooksの深い理解と使用
  • Reactフックの仕組み

<<:  DOSBox を起動後に自動的にコマンドを実行する方法

>>:  mysql 解凍パッケージの基本インストールチュートリアル

推薦する

MySQL のデバッグと最適化に関する 101 のヒントを共有する

MySQL は強力なオープンソース データベースです。データベース駆動型アプリケーションの数が増える...

Dockerをクリーンアンインストールする方法の詳細な説明

まず、サーバー環境情報: アンインストールの理由:しばらくするとホストマシンのディスクが100%にな...

CentOSにDockerをインストールする方法

ここでは比較的簡単なインストール方法のみを紹介します。 1. yumを使用してインストールするyum...

Windows Server 2019 のインストール (グラフィカル チュートリアル)

Windows Server 2019 は、Microsoft が公式にリリースした最新のサーバー...

MySQL の分離レベルの包括的な分析

データベースが同じデータ バッチを同時に追加、削除、および変更すると、ダーティ書き込み、ダーティ読み...

React のネストされたコンポーネントの構築順序

目次Reactの公式サイトではライフサイクルの説明を見ることができます次に、ネストされたコンポーネン...

Dockerコンテナの起動失敗を解決する方法

質問: コンピュータを再起動した後、docker の mysql コンテナを再起動できません。原因が...

HTML でマウスが停止したときに行全体の色 (tr) を変更する方法

純粋な CSS を使用して、マウスが行の上を通過するときに行の背景色を変更し、その行にフォーカスがあ...

Dockerでの接続例外中のエラーを解決する

Docker を初めて使い始めると、通常とは異なる問題に遭遇して、必然的に混乱してしまいます。大丈夫...

protobuf の簡単な紹介と Ubuntu 16.04 環境でのインストールチュートリアル

protobufの簡単な紹介Protobuf は、Google のオープンソースのシリアル化プロトコ...

Vue3 での provide と injection の使用

1. provideとinjectの説明Provide と Inject により、ネストされたコンポ...

角度に基づくツリー型セカンダリテーブルを実現する

まず効果を見てみましょう: コード: 1.html <div class="user...

Centos8環境でSSHポート番号を変更する方法

目次序文始める序文サーバーのデフォルトの SSH ポート番号は通常 22 であるため、ほとんどのユー...

vscode で Prettier Code プラグインを使用する詳細なチュートリアル

なぜprettierを使うのですか?大企業では、フロントエンド開発コードに独自のコード標準がある場合...

CSS3で実装されたグラデーションスライド効果

成果を達成する コードhtml <div class="css-slideshow&...