ReactのuseCallbackフックを飼いならす方法を学ぶ
公開: 2022-04-27React.jsが近年広く普及していることは周知の事実です。 これは、FacebookやWhatsAppなど、インターネットで最も著名なプレーヤーの多くが選択するJavaScriptライブラリになりました。
その上昇の主な理由の1つは、バージョン16.8でのフックの導入でした。 Reactフックを使用すると、クラスコンポーネントを記述せずにReact機能を利用できます。 現在、フックを備えた機能コンポーネントは、Reactを操作するための開発者の頼りになる構造になっています。
このブログ投稿では、メモ化と呼ばれる関数型プログラミングの基本的な部分に触れているため、1つの特定のフックであるuseCallback
について詳しく説明します。 useCallback
フックをいつどのように利用し、パフォーマンス向上機能を最大限に活用するかを正確に知ることができます。
準備? 飛び込みましょう!
メモ化とは何ですか?
メモ化とは、複雑な関数がその出力を保存するときなので、次に同じ入力で呼び出されたときです。 キャッシュに似ていますが、よりローカルなレベルです。 複雑な計算をスキップして、すでに計算されているため、出力をより速く返すことができます。
これは、メモリの割り当てとパフォーマンスに大きな影響を与える可能性があり、その負担は、 useCallback
フックが軽減することを目的としています。
ReactのuseCallbackとuseMemo
この時点で、 useMemo
useCallback
呼ばれる別のフックとうまくペアになっていることを言及する価値があります。 両方について説明しますが、この記事では、メイントピックとしてuseCallback
に焦点を当てます。
主な違いは、 useMemo
はメモ化された値を返すのに対し、 useCallback
はメモ化された関数を返すことです。 つまり、 useMemo
は計算値を格納するために使用され、 useCallback
は後で呼び出すことができる関数を返します。
これらのフックは、依存関係(stateやpropsなど)の1つが変更されない限り、キャッシュされたバージョンを返します。
実際の2つの関数を見てみましょう。
import { useMemo, useCallback } from 'react' const values = [3, 9, 6, 4, 2, 1] // This will always return the same value, a sorted array. Once the values array changes then this will recompute. const memoizedValue = useMemo(() => values.sort(), [values]) // This will give me back a function that can be called later on. It will always return the same result unless the values array is modified. const memoizedFunction = useCallback(() => values.sort(), [values])
上記のコードスニペットは不自然な例ですが、2つのコールバックの違いを示しています。
-
memoizedValue
は配列[1, 2, 3, 4, 6, 9]
になります。 値変数が残っている限り、memoizedValue
も残り、再計算されることはありません。 -
memoizedFunction
は、配列[1, 2, 3, 4, 6, 9]
1、2、3、4、6、9]を返す関数になります。
これらの2つのコールバックの優れている点は、依存関係の配列が変更されるまでキャッシュされてハングアップすることです。 これは、レンダリング時にガベージコレクションが行われないことを意味します。
レンダリングと反応
Reactに関して、メモ化が重要なのはなぜですか?
これは、Reactがコンポーネントをレンダリングする方法と関係があります。 Reactは、メモリに保存されている仮想DOMを使用してデータを比較し、何を更新するかを決定します。
仮想DOMは、パフォーマンスに対応し、アプリケーションを高速に保つのに役立ちます。 デフォルトでは、コンポーネントの値が変更されると、コンポーネント全体が再レンダリングされます。 これにより、Reactはユーザー入力に対して「リアクティブ」になり、ページをリロードせずに画面を更新できるようになります。
変更はそのコンポーネントに影響を与えないため、コンポーネントをレンダリングする必要はありません。 ここで、 useCallback
とuseMemo
によるメモ化が役立ちます。
Reactがコンポーネントを再レンダリングすると、コンポーネント内で宣言した関数も再作成されます。
関数の同等性を別の関数と比較する場合、それらは常にfalseになることに注意してください。 関数もオブジェクトであるため、それ自体と等しくなるだけです。
// these variables contain the exact same function but they are not equal const hello = () => console.log('Hello Matt') const hello2 = () => console.log('Hello Matt') hello === hello2 // false hello === hello // true
つまり、Reactがコンポーネントを再レンダリングすると、コンポーネントで宣言されているすべての関数が新しい関数であると見なされます。
これはほとんどの場合問題なく、単純な関数は計算が簡単で、パフォーマンスに影響を与えません。 ただし、関数を新しい関数として表示したくない場合は、 useCallback
を使用して支援することができます。
「関数を新しい関数として見られたくないのはいつですか?」とお考えかもしれません。 ええと、 useCallback
がより理にかなっている特定のケースがあります:
- 関数をメモ化されている別のコンポーネントに渡しています(
useMemo
)。 - 関数には、覚えておく必要のある内部状態があります
- あなたの関数は、たとえば
useEffect
のような別のフックの依存関係です
ReactuseCallbackのパフォーマンス上の利点
useCallback
を適切に使用すると、アプリケーションを高速化し、必要のない場合にコンポーネントが再レンダリングされるのを防ぐことができます。
たとえば、大量のデータをフェッチし、そのデータを次のようにグラフまたはグラフの形式で表示するコンポーネントがあるとします。
データ視覚化のコンポーネントの親コンポーネントが再レンダリングされたが、変更された小道具や状態はそのコンポーネントに影響を与えないとします。 その場合、おそらくそれを再レンダリングしてすべてのデータを再フェッチする必要はありません。 この再レンダリングと再フェッチを回避すると、ユーザーの帯域幅を節約し、よりスムーズなユーザーエクスペリエンスを提供できます。
ReactuseCallbackの欠点
このフックはパフォーマンスの向上に役立ちますが、落とし穴もあります。 useCallback
(およびuseMemo
)を使用する前に考慮すべき点は次のとおりです。
- ガベージコレクション:まだメモ化されていない他の関数は、メモリを解放するためにReactによって破棄されます。
- メモリ割り当て:ガベージコレクションと同様に、メモ化された関数が多いほど、必要なメモリも多くなります。 さらに、これらのコールバックを使用するたびに、React内に大量のコードがあり、キャッシュされた出力を提供するためにさらに多くのメモリを使用する必要があります。
- コードの複雑さ:これらのフックで関数をラップし始めると、すぐにコードの複雑さが増します。 これらのフックが使用されている理由をより深く理解し、正しく使用されていることを確認する必要があります。
上記の落とし穴に注意することで、自分でそれらに遭遇するという頭痛の種を減らすことができます。 useCallback
の採用を検討するときは、パフォーマンス上の利点が欠点を上回っていることを確認してください。
useCallbackの例を反応させる
以下は、ButtonコンポーネントとCounterコンポーネントを使用した簡単なセットアップです。 Counterには2つの状態があり、それぞれがCounterコンポーネントの状態の個別の部分を更新する2つのButtonコンポーネントをレンダリングします。
Buttonコンポーネントは、 handleClick
とnameの2つの小道具を取ります。 ボタンがレンダリングされるたびに、コンソールにログが記録されます。
import { useCallback, useState } from 'react' const Button = ({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> } const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) return ( <> {countOne} {countTwo} <Button handleClick={() => setCountOne(countOne + 1)} name="button1" /> <Button handleClick={() => setCountTwo(countTwo + 1)} name="button1" /> </> ) }
この例では、いずれかのボタンをクリックするたびに、コンソールに次のように表示されます。
// counter rendered // button1 rendered // button2 rendered
ここで、 useCallback
をhandleClick
関数に適用し、ボタンをReact.memo
でラップすると、 useCallback
が提供するものを確認できます。 React.memo
はuseMemo
に似ており、コンポーネントをメモ化することができます。
import { useCallback, useState } from 'react' const Button = React.memo(({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> }) const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) const memoizedSetCountOne = useCallback(() => setCountOne(countOne + 1), [countOne) const memoizedSetCountTwo = useCallback(() => setCountTwo(countTwo + 1), [countTwo]) return ( <> {countOne} {countTwo} <Button handleClick={memoizedSetCountOne} name="button1" /> <Button handleClick={memoizedSetCountTwo} name="button1" /> </> ) }
これで、いずれかのボタンをクリックすると、コンソールにログインするためにクリックしたボタンのみが表示されます。
// counter rendered // button1 rendered // counter rendered // button2 rendered
ボタンコンポーネントにメモ化を適用しました。ボタンコンポーネントに渡されるprop値は等しいと見なされます。 2つのhandleClick
関数はキャッシュされ、依存関係配列の項目の値が変更されるまで(たとえば、 countOne
、 countTwo
)、Reactによって同じ関数と見なされます。
概要
useCallback
とuseMemo
はクールですが、特定のユースケースがあることを忘れないでください。すべての関数をこれらのフックでラップするべきではありません。 関数の計算が複雑な場合は、メモ化されたコンポーネントに渡される別のフックまたは小道具の依存関係が、 useCallback
のために到達したい可能性のある優れた指標です。
この記事が、この高度なReact機能を理解し、その過程で関数型プログラミングに自信を持てるようになることを願っています。