Background
- 自分の理解と初学者向けにuseEffectの必要性や使い方をまとめて行く
What is useEffect?
- 関数コンポーネント内で副作用(side-effect)を実行するためのHook
- 副作用(side-effect)とは、コンポーネントの出力(レンダリング)に関係ない処理のこと
- useEffectを用いることでレンダリングと副作用を分離することが可能
How to use
- 第1引数: 副作用関数
- 第2引数: 副作用関数の実行タイミングを制御する依存配列。値により第1引数の関数の実行タイミングをコントロールできる
No | 第2引数の値 | 実行タイミング | サンプル |
---|---|---|---|
1 | 指定なし | レンダーの完了時に毎回実行される(危険) | useEffect(funcA) |
2 | 空の配列 | コンポーネントのマント時とアンマウント時 | useEffect(funcA, []) |
3 | 値の配列 | 最初のマウント時と与えられた値の変化時 | useEffect(funcA, [value] |
useEffect(func1, []);
- 例
useEffect(() => {
console.log('Executed side-effect function')
}, [])
Why that's necessary?
- useEffectを使わない場合、Appは副作用とレンダリングが分離されておらず、レンダリングの度にログが出力される
import React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const addCount = () => setCount(count + 1);
console.log("function App called!");
return (
<div className="App">
<p>count: {count}</p>
<button onClick={() => addCount()}>click me</button>
</div>
);
}
- 前述の
function App()
を下記のように実装することで、コンポーネントのマウント時とアンマウント時のみログが出力されるようになる
import React, { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
const addCount = () => setCount(count + 1);
// useEffectの第2引数に空の配列を渡すことで、コンポーネントのマウント時とアンマウント時のみログ出力される
useEffect(() => {
console.log("function App called!");
}, []);
return (
<div className="App">
<p>count: {count}</p>
<button onClick={() => addCount()}>click me</button>
</div>
);
}
Points of second argument
- 上述の通り、コンポーネントのMount時のみに実行させたい場合、第2引数に
[]
を指定すれば良い - しかし、第2引数に
[]
を指定できるのは下記の条件を満たしている場合のみ - 第1引数に指定した副作用関数で
prop
やstate
を参照していない場合 - 通常、このようなケースは限定的なので、副作用関数が
prop
やstate
を利用する場合の、第2引数の指定の仕方について記載する
useEffect(() => {
function doSomething() {
console.log('Hello');
}
doSomething();
}, []); //It's OK because we don't use any props and states in `doSomething` function.
Case1: useEffect内で副作用関数を定義し、第2引数に依存するprop
やstate
を指定する
- 副作用関数が
props
やstate
を使う場合は、useEffec内で宣言することが望ましい - この場合、副作用関数が依存する
prop
や、state
を第2引数に指定する必要がある
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]);
Case2: useEffect外にuseCallback
でラップした副作用関数を定義し、第2引数に副作用関数を指定する
- 通常Javascriptでは関数は都度生成されるため、レンダリングの際に新しい関数が生成される
- useCallbackを使うことで、関数自体の依存が変わらない場合に、関数が変化しないことを保証できる
- これにより、第2引数に副作用関数を指定しても、関数の依存が変わらない限りuseEffectが実行されない
function ProductPage({ productId }) {
// Wrap with useCallback to avoid change on every render
const fetchProduct = useCallback(() => {
// ... Does something with productId ...
}, [productId]); // All useCallback dependencies are specified
return <ProductDetails fetchProduct={fetchProduct} />;
}
function ProductDetails({ fetchProduct }) {
useEffect(() => {
fetchProduct();
}, [fetchProduct]); // Important! we should add fetchProduct function to second argument
}
Case2の派生版: useEffect外に定義した、propやstateに依存する副作用関数をマウント時のみ実行させる
-
useRef
を活用してコンポーネントのmountフラグを作成して、それをuseCallback
の第2引数に設定する - これにより、
useCallback
でラップした関数はマウント時のみ作成され、以降は変化しないため副作用関数もマウント時のみ実行される
//マウントフラグを返す関数
function useIsMountedRef() {
// マウント時にフラグを設定
// useRefで設定した値は固定化され、レンダリングに関与しない
const isMounted = useRef(true);
// クリーンアップ関数を定義することでアンマウント時にフラグを`false`に設定
useEffect(() => () => {
isMounted.current = false;
}, []);
return isMounted;
}
function ProductPage({ productId }) {
// 追加
const isMountedRef = useIsMountedRef();
// Wrap with useCallback to avoid change on every render
const fetchProduct = useCallback(() => {
// ... Does something with productId ...
}, [isMountedRef]); // Mount時のみ関数が生成されるようになる
return <ProductDetails fetchProduct={fetchProduct} />;
}
function ProductDetails({ fetchProduct }) {
useEffect(() => {
fetchProduct();
}, [fetchProduct]); // Important! we should add fetchProduct function to second argument
}