理解のポイントと前提
- 描画サイクルの理解
Reactアプリケーションは「データの更新⇨コンポーネントの再描画⇨その他の処理」の一連のサイクルを繰り返す - 「その他の処理」の実装方法
useEffect, useLayoutEffect, useMemo, useCallback, useReducerを使う - 描画関数の実態は関数である
- 「依存配列」という仕組みを使うことで、「その他の処理」の実行タイミングを細かく定義できる
- return文を書くことで、描画コンポーネントがアンマウントされた時の処理を定義できる(useEffect, useLayouEffect)
- useEffectとuseLayoutEffectは仲間
- 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 (
<>
{/* 省略 */}
</>
);
}
補足
- 第一引数にコンポーネント描画後に実行される関数を渡す。
- 第二引数に依存するステート値を配列で渡す。第二引数に渡す配列を依存配列という。
- 空の依存配列を渡すと、コンポーネントの初期描画時のみ第一引数で渡した関数が実行される。
- 第二引数は省略可能。省略すると、描画完了後ごとに実行される。
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関数
後日書きます。