1
Help us understand the problem. What are the problem?

posted at

React-useEffect, useLayoutEffect, useMemo, useCallback, useReducerの使い方

理解のポイントと前提

  1. 描画サイクルの理解
    Reactアプリケーションは「データの更新⇨コンポーネントの再描画⇨その他の処理」の一連のサイクルを繰り返す
  2. 「その他の処理」の実装方法
    useEffect, useLayoutEffect, useMemo, useCallback, useReducerを使う
  3. 描画関数の実態は関数である
  4. 「依存配列」という仕組みを使うことで、「その他の処理」の実行タイミングを細かく定義できる
  5. return文を書くことで、描画コンポーネントがアンマウントされた時の処理を定義できる(useEffect, useLayouEffect)
  6. useEffectとuseLayoutEffectは仲間
  7. useMemoとuseCallbackは仲間

useEffect関数

Reactコンポーネントの描画が完了した後に実行される関数

export default function App() {
	const [val1, setVal1] = useState("");
	const [val2, setVal2] = useState("");

	// 描画完了後ごとに実行される
	useEffect(() => {
	  console.log("first render");
	});
	
	// 初回描画完了後に実行される
	useEffect(() => {
	  console.log("first render");
	},[]);

	// 初回描画完了後に実行と
	// またはAppコンポーネントがアンマウントされた時returnで返した関数が実行される
	useEffect(() => {
	  console.log("first render");
	  return console.log('unmounted component');
	},[]);

	// 初回描画完了後と
	// val1の値が変化したら実行される	
	useEffect(() => {
	  console.log(val1);
	},[val1]);
  
	// 初回描画完了後と
	// val2の値が変化したら実行される
	useEffect(() => {
	  console.log(val2);
	},[val2]);
  
	return (
	  <>
	  {/* 省略 */}
	  </>
	);
  }

補足

  1. 第一引数にコンポーネント描画後に実行される関数を渡す。
  2. 第二引数に依存するステート値を配列で渡す。第二引数に渡す配列を依存配列という。
  3. 空の依存配列を渡すと、コンポーネントの初期描画時のみ第一引数で渡した関数が実行される。
  4. 第二引数は省略可能。省略すると、描画完了後ごとに実行される。

useLayoutEffect関数

使い方はuseEffectと同じ。コンポーネントの描画がされる前に実行される。

useMemo関数

第二引数にフックしたい値を依存配列として渡し、渡した変数が変化したら第一引数の関数が実行される。依存配列を使ってフックしたい値を第二引数に定義するのでuseEffect, useLayoutEffectと似ている。
ではなぜuseMemo関数があるのかというと、依存配列に渡された値が同一かどうか(つまり変化していないかどうか)のチェックには二種類あり、場合によってはuseEffect, useLayoutEffectが意図しない動作になることがあるからである。以下Javascriptの仕様である。

// プリミティブ値の場合=文字列,真偽値,数値の場合
const val1 = 'apple'
const val2 = 'apple'
const boo1 = true
const boo2 = true

// 参照値の場合=オブジェクト,配列,関数の場合
const array1 = ['hoge', 'huga', 'piyo']
const array2 = ['hoge', 'huga', 'piyo']
const fun1 = () => console.log('hoge')
const fun2 = () => console.log('hoge')

// プリミティブ値の場合は値が同一であるかどうかで評価される
console.log(val1 == val2) // true
console.log(boo1 == boo2) // true
// 参照値の場合はインスタンスが同一であるかどうかで評価される
console.log(array1 == array2) // false
console.log(fun1 == fun2) // false

なので、フックしたい値が参照値(オブジェクト,配列,関数)でかつ参照値をコンポーネント内で生成したい場合は以下のようにする。


// useEffectを使った場合は、意図しない挙動になる
function App({ letter = "" }) {  
	// 例えletterの値が同じでも毎回異なるインスタンスが生成される
	const words = letter.split(" ");

	// なのでletterの値が同じでもconsole.logが実行される
	useEffect(() => {
		console.log("fresh render");
	}, [words]);

	return (
		<>
		{/* 省略 */}
		</>
	);
}

// useMemoを使った場合、wordsには常に同じインスタンスとして代入される
function App({ letter = "" }) {
	// useMemoによるメモ化
	// 第二引数の値が変化したら、第一引数が実行され値はwordsに「キャッシュされる」(同じインスタンスとして)
	const words = useMemo(() => letter.split(" "), [letter]);
  
	useEffect(() => {
	  console.log("fresh render");
	}, [words]);
  
	return (
	  <>
		{/* 省略 */}
	  </>
	);
}

useCallBack

useMemoとよく似ている。useMemoがキャッシュされた「値」を返すのに対して、useCallbackはキャッシュされた「関数」を返す。

function App() {
	// 依存配列が空なので、初回描画時にのみ実行される
	const fn = useCallback(() => {
	  console.log("hello");
	  console.log("world");
	}, []);
	
	// 初回描画時の後に実行される
	// また第二引数のfnが変化したら実行される
	// fnは初回描画時にキャッシュされているので
	// 結果的にfnは不変で、コンソール出力は一度しか実行されない
	useEffect(() => {
	  console.log("fresh render");
	  fn();
	}, [fn]);
  
	return (
		<>
		{/* 省略 */}
		</>
	);
}

useReducer関数

後日書きます。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
1
Help us understand the problem. What are the problem?