- 参考
- React hookとは
- React hookのイメージ
- 前提として知っておきたいこと
- useState
- useEffect
- useMemo
- React.memoとは
- useCallback
- まとめ
参考
React hookとは
状態管理や副作用などのReactの機能を、クラスを書かずに使えるようにする機能。
かつては状態管理や副作用を使うにはクラスコンポーネントとというものを利用していたらしい。
関数コンポーネントの純粋性を保つために、状態管理や副作用を、関数コンポーネント内ではなく、React hookに任せる。
React hookのイメージ
Reach hookを理解するにはコンポーネントが関数であることを思い出すと理解しやすい。
React hookは基本的に関数の外にあるものを管理する。
例えば、Stateは関数外の変数。
関数コンポーネントはステートレスな純粋関数だから、Stateは関数外部で管理するということらしい。
useState()は関数外部のgetter/setterを呼び出しているイメージ。
でも、純粋関数って関数外部の変数を参照も変更もしないはず。。。よくわからず。
前提として知っておきたいこと
1,関数コンポーネントはStateが更新される度に再実行(再レンダリング)される。
→useMemoで必要な知識
2.親コンポーネントが再実行(再レンダリング)されると、子コンポーネントも再実行される。
3.コールバック関数を Props として受け取った関数コンポーネントは必ず再レンダリングされる。
→useCallbackで必要な知識
→useMemoを使えば再レンダリングされなかった。React.memoだとされる。
4.JSXの <Child /> はコンポーネント呼び出し
関数呼び出しと同じ意味。
6.関数コンポーネント再実行されると、関数内のオブジェクト(コールバック関数含む)は再生成され、参照値が変わる。→React.memoで必要な知識
//Example関数実行の度に、中のオブジェクトは再生成され、参照値が変わる const Example = () => { const obj = {name: "takeshi", age:20};// オブジェクト const callbackFunc = () => { console.log("関数オブジェクト"); } return ( <h3>abc</h3> ); };
useState
useState
は、関数コンポーネント内で状態(State)管理を行うためのフックです。具体的には、変数を扱うことができます。
状態(State)を更新すると、関数コンポーネントが再実行(再レンダリング)され、即座にブラウザ上にも反映されます。
・構文
const [状態変数, 状態更新用関数] = useState(状態の初期値); const [state, setState] = useState(0);//初期値0で初期化 seState(1); // stateを1に更新
コード例
setCount
メソッドを使ってcount
の値を更新できます。例えば、ボタンをクリックしたらcount
を増減させることができます。
import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0);//初期化 const [str, setStr] = useState("Hello!");//文字列も可 const increment = () => setCount(count + 1);//状態更新 const decrement = () => setCount(count - 1);//状態更新 return ( <div> <h1>Counter</h1> <h2>カウント: {count}</h2> <button onClick={increment}>+</button> <button onClick={decrement}>-</button> </div> ); }
Stateの特徴と注意点
■特徴
- ステートが変わると再レンダリングされる(重要!!!)
- ステートは再レンダリング後も保持。F5で画面全体再更新すると消える。
- ステートはコンポーネント毎に保持
- ステートはpropsで渡せる
- ステートを更新する時は更新用関数を使う
■注意点
- コンポーネントの中で呼び出す
- if/for文の中で呼び出さない
- 状態更新は即時ではない(予約)
- オブジェクト型のステート変更する時は全プロパティを変更する
- オブジェクト型と配列のステート変更時は、新しいオブジェクトを渡す
- コンポーネントが消えるとステートも初期化される
Stateについて詳しくはこちら↓
React基礎 - okinawa
useEffect
useEffect
は、関数コンポーネント内で副作用を実行するためのフックです。
副作用とは、fetch関数を使って外部のリソースからデータを取得したり、DOMの更新、ロギング(console.log
も含む)などの処理を指します。
・構文
useEffect(() => { // 依存配列変更後に実行したい処理 console.log("副作用処理") },[依存配列]);
依存配列に変更があった時のみ、useEffect内の処理が実行されます。
依存配列未指定の場合は、初レンダリング時のみ実行されます。
useEffectの使い所
State変更後に何らかの処理を実行したい時。
コード例
ボタン押す度にcountが変更し、useEffect内の処理が実行される。
const Example = () => { const [count, setCount] = useState(0); useEffect(() => { console.log("useEffect内実行"); }, [count]);//Stateであるcountが依存配列 return ( <> <p>{count}</p> <button onClick={() => setCount(count + 1)}>ボタン</button> </> ) } export default Example;
useMemo
useMemoは関数の計算結果を保存する。
関数自体を保存するわけではなく、値だけを保存する。(useCallbackは関数自体を保存する)
数値でも文字列でもJSXでもなんでも保存できますよ。
・構文
useMemo(() => { return 重い処理 },[依存配列])
依存配列に変更があったときのみ、useMemo内の処理が実行される。
そして、return結果がMemoに保存される。
依存配列未指定の場合は、初回レンダリング時のみ実行される。
useMemoの使い所
重い処理の結果を保存しておき、再利用する。
例えば、関数Aの中で2つの関数を呼び出していたとして
- 処理の重い関数B
- 処理の軽い関数C
関数Aを呼び出すと、BもCもどちらも実行される。
でも、重いBは必要なときだけ呼び出したいんだよなあ、という時に使う。
function 関数コンポーネントA() { // 関数Bの処理は非常に重いので、必要なときだけ呼び出したい 関数B(); // 関数Cの処理は軽いので、毎回呼び出してOK 関数C(); }
例:5億回のループは必要なときだけにしたい
・useMemoを使わない場合
軽い処理ボタンを押すと、lightStateが更新される。
Stateが変わると再レンダリング、つまり関数Exampleが再実行されるので、heavyFunc(重い処理)も呼び出されてしまい、レンダリングに時間がかかる。
import { useEffect, useCallback, useMemo, useState } from 'react'; //Stateが更新されるとExample関数が再実行される const Example = () => { const [heavyState, setHeavyState] = useState(1); const [lightState, setLightState] = useState(0); const lightFunc = () => { setLightState(prev => prev + 1); return lightState; } //heavyState * 5億回のループ const heavyFunc = (heavyState) => { let result = 0; for (let i = 0; i <= heavyState * 500000000; i++) { result = i; } return result; } // レンダリングの度に重い処理を呼び出す const result2 = heavyFunc(heavyState); return ( <> <p>{lightState}</p> <button onClick={lightFunc}>軽い処理</button> <p>{heavyState} * 500000000 = {result2}回のループ</p> <button onClick={() => setHeavyState(prev => prev + 1)}>重い処理</button> </> ); }; export default Example;
↓ useMemo()を使うバージョン
useMemoでheavyFuncの結果を保存して再利用可能にしておく。
heavyFuncは、heavyStateの値が変わった時、つまり重い処理ボタン押したときのみ実行される。
import { useEffect, useCallback, useMemo, useState } from 'react'; //Stateが更新されるとExample関数が再実行される const Example = () => { const [heavyState, setHeavyState] = useState(1); const [lightState, setLightState] = useState(0); const lightFunc = () => { setLightState(prev => prev + 1); return lightState; } //heavyState * 5億回のループ const heavyFunc = (heavyState) => { let result = 0; for (let i = 0; i <= heavyState * 500000000; i++) { result = i; } return result; } // 変更箇所はここのみ // useMemoで結果をキャッシュする // Example関数が再実行されても、heavyState変わってなければ、再実行されない // 再実行しない場合はキャッシュした結果を使う const result2 = useMemo(() => { return heavyFunc(heavyState); },[heavyState]) return ( <> <p>{lightState}</p> <button onClick={lightFunc}>軽い処理</button> <p>{heavyState} * 500000000 = {result2}回のループ</p> <button onClick={() => setHeavyState(prev => prev + 1)}>重い処理</button> </> ); }; export default Example;
useMemoの注意点
コンポーネントをメモ化する場合は注意。
特にコンポーネント丸ごとuseMemoに入れちゃえばええやん!とやるとハマりがち。
以下のコードは、ボタンクリックでコンソールに「click」と表示するものだが、機能しない。
なぜなら、ボタンが丸ごとメモ化されているので、依存配列が変わらない限り、useMemo内の処理は実行されない。
こういう時はReact.memoを使う。
import React, { useEffect, useCallback, useMemo, useState } from 'react'; function Child({handleClick}) { const memo = useMemo((handleClick) => { return <button onClick={handleClick}>Child</button>; },[handleClick]);//依存配列に関数を入れれば行けそうな気がするがダメ。クリックしても関数自体が変化するわけではないので return memo; } export default function App() { const handleClick = () => { console.log("click"); }; return ( <> <Child handleClick={handleClick} /> </> ); }
React.memoとは
※React.memoはhookではないが、useMemoと似ていてごっちゃになるので記載する。
React.memoは不要な再レンダリングを防ぐためのもの。
コンポーネントのpropsが変更された場合のみ、再レンダリングを行う。
propsが変更されない限り、前回のメモを再利用して再レンダリングを回避する。
React.memoとuseMemoの使い所
- React.memo=コンポーネントをメモ
- useMemo=重い処理結果をメモ
・使い所イメージ(React.memo)
親コンポーネントA { // 重いレンダリング処理B 子コンポーネントB() // 軽い処理 関数C() }
親実行時に子Bが再レンダリングされるのを回避したいとき使う。-
・React.memoとuseMemoの再実行条件
useMemoは依存配列が変わった時のみ。
React.memoはpropsが変わった時。ちと複雑。
・propsの変更判定について
propsの値が変わってなくても、参照値が変わっていれば変更したと判定される。
例えば、親コンポーネント関数が再実行されると、親から渡されるpropsも再生成されるので、参照値が変わる。
参考
qiita.com
コード例
・React.memo未使用↓
parentボタンを押すとChildコンポーネントが再レンダリングされてしまう。
function Child() { console.log("Child再実行"); return <p>Childコンポーネント</p> } export default function App() { const [count, setCount] = useState(0); const countUp = () => { setCount(prev => prev + 1); } return ( <> <p>{count}</p> <button onClick={countUp}>parent</button> <Child /> </> ); }
・React.memo使用↓
parentボタン押しても、Chilidが再レンダリングされない。
const Child = React.memo((props) => { console.log("Child再実行") // コンポーネントをメモ化 return ( <> <p>Childコンポーネント</p> </> ); }); export default function App() { const [count, setCount] = useState(0); const countUp = () => { setCount(prev => prev + 1); } return ( <> <p>{count}</p> <button onClick={countUp}>parent</button> <Child /> </> ); }
React.memoとuseMemoの違いまとめ
どっちも結果をメモする機能。
useMemo
:useMemo
は、値をメモ化するためのReactフックです。- 計算結果の値をキャッシュし、同じ計算を繰り返さないようにします。
- 例えば、重い計算やデータの加工が必要な場合、その結果を保持して再利用できます。
- 依存配列の変更に応じて再計算を行います。
React.memo
:useMemo
の例:
const expensiveValue = useMemo(() => { // 重い計算やデータの加工 return computeExpensiveValue(); }, [dependency1, dependency2]);
React.memo
の例:
const MyComponent = React.memo(props => { // レンダリングコストが高いコンポーネント return <div>{props.data}</div>; });
React.memoの詳細についてはこちら↓
React.memo / useCallback / useMemo の使い方、使い所を理解してパフォーマンス最適化をする #JavaScript - Qiita
両者の違いはこちらがわかりやすい↓
yukimasablog.com
React.memoの注意点(意図しない再レンダリング)
propsの値が変わるとメモを使用せず、再実行してしまう。
中身の値が変わらず、参照値が変わるだけでも再実行。こういう時はuseCallbackを使うと再実行を防げる。
import React, { useEffect, useCallback, useMemo, useState } from 'react'; const Child = React.memo(({func}) => { //メモ内に親から渡されたオブジェクトがあるので、親再実行すると、memo内処理も実行される。 console.log("メモ内実行"); return <button onClick={func}>Child</button>; }); export default function App() { //再レンダリング時に関数オブジェクトが再生成され、参照値が変わる const callbackFunc = () => { console.log("関数オブジェクト"); } return ( <Child object={func={callbackFunc} /> )
面倒だから全部useMemoに統一したいけどダメ?
残念ながらダメ。
useMemoの注意点で書いたケースはReact.memoを使う。
useCallback
※React.memoとの併用で使う。useMemoしか使っていないならuseCallbackの使い所はほぼない。
React.memoの特徴としてメモ内のpropsに変更があると、再レンダリングされる。
例えばコールバック関数がpropsとして渡されていると、親コンポーネント再レンダリング→propsも再生成で参照値変わる→React.memoしてても子コンポーネント再レンダリング
これを防ぐためにuseCallbackを使う。
詳しくは、リンクの「Reactコンポーネントの再レンダリング条件検証」を見るとわかる。
useCallbackの使い所
React.memoしているのに、子コンポーネントが再レンダリングしてしまうのを防ぎたい時。
関数をメモ化(保存)することで、参照値が変わらないようにする。
・使用方法
子にpropsとして渡すコールバック関数を親側でメモ化しておき、親再レンダリング時にコールバック関数を再生成しない。
そうすると、メモされたコールバック関数は、毎回同じオブジェクト判定なので、子のReact.memoに渡しても、React.memo内が再実行されない。(子が再レンダリングされない)
・構文
const memoCallbackFunc = useCallback(() => { // 第一引数は何らかの処理 console.log("あいうえお"); },[依存配列])
依存配列に変更があったときのみ、useCallback内の処理を保存し直す。(処理を実行はしない)
処理自体が保存されるので、returnはなくてもよい。
依存配列未指定の場合は、初回レンダリング時のみ保存される。
・useCallback未使用の例
参考
React.memo / useCallback / useMemo の使い方、使い所を理解してパフォーマンス最適化をする #JavaScript - Qiita
import React, { useState } from "react"; const Child = React.memo(props => { console.log("render Child"); return <button onClick={props.handleClick}>Child</button>; }); export default function App() { console.log("render App"); const [count, setCount] = useState(0); // 関数はコンポーネントが再レンダリングされる度に再生成されるため、 // 関数の内容が同じでも、新しい handleClick と前回の handleClick は // 異なるオブジェクトなので、等価ではない。 // そのため、コンポーネントが再レンダリングされる。 const handleClick = () => { console.log("click"); }; return ( <> <p>Counter: {count}</p> <button onClick={() => setCount(count + 1)}>Increment count</button> <Child handleClick={handleClick} /> </> ); }
・useCallback使用例
参考
React.memo / useCallback / useMemo の使い方、使い所を理解してパフォーマンス最適化をする #JavaScript - Qiita
import React, { useState, useCallback } from "react"; const Child = React.memo(props => { console.log("render Child"); return <button onClick={props.handleClick}>Child</button>; }); export default function App() { console.log("render App"); const [count, setCount] = useState(0); // 関数をメモ化すれば、新しい handleClick と前回の handleClick は // 等価になる。そのため、Child コンポーネントは再レンダリングされない。 const handleClick = useCallback(() => { console.log("click"); }, []); return ( <> <p>Counter: {count}</p> <button onClick={() => setCount(count + 1)}>Increment count</button> <Child handleClick={handleClick} /> </> ); }
まとめ
- useState:状態管理
- useEffect:状態変更後に実行したい処理
- useMemo:重い処理の結果保存
- React.memo:重いコンポーネント保存
- useCallback:コールバック関数保存。React.memoと併用
React.memoとuseCallbackの関係だけややこしい。
- React.memoとuseCallbackはセットで使うよ
- React.memoの再実行条件はpropsオブジェクトに変更があった時
- propsオブジェクトは、参照値が変わっただけで変更したと判定されるよ
- だからuseCallback使って参照値も変わらないようにするよ