はじめに
おはようございます、Tunです。
今回は、副作用について記載していきます。
副作用(useEffect)
useEffect
は、React Hooksの機能群のひとつで、コンポーネントの副作用を制御する機能です。Reactからimport
すると使用できます。
import { useEffect } from "react";
//以下構文
useEffect(実行する関数,[依存する値]);
このuseEffect
の役割はある値が変わったときに限り、ある処理を実行する機能になります。
以下の例を見てみましょう。
export const App = () => {
useEffect(() => {
alert();
},[num]);
return(
{/* 省略 */ }
);
};
第1引数にはアロー関数で処理を記述し、第2引数には必ず配列で指定して、複数指定する場合は[num, num2]のように書きます。
注意点としてuseEffect
は依存配列に指定している値が変わったときに加え、コンポーネントのマウント時(一番最初)にも必ず実行されます。useEffect
の第2引数を設定すると、画面を表示して初期データを取得する時など、「コンポーネントを表示した初回のみ実行するような処理」を記述できます。
ここから公式HPの解説に移ります。
副作用フック
Reactコンポーネントに内部から、外部データの取得や購読(subscription)、手動でのDOM更新の操作は、他のコンポーネントに影響することがあり、又レンダーの最中に実行することが出来ないので、このような操作のことを副作用(side-effects) 、または省略して作用(effects) と呼びます。
useEffect
は副作用のためのフックであり、関数コンポーネント内で副作用を実行することができます。クラスコンポーネントにおけるcomponentDidMount
、componentDidUpdate
およびcomponentWiiUnmount
と同様の目的で使うもものですが、一つのAPIに統合されています。詳しくはこちら
import React, { seState, useEffect } from 'react';
function Example(){
const [count, setCount] = useState(0);
useEffect(() => {
//ブラウザAPIを使用してドキュメントのタイトルを更新する
document.title = `You clicked ${count} times`;
});
return(
<div>
<p>You clicked {count} times</p>
<button onClicked={() => setCount(count +1)}>
Click me
</button>
</div>
);
}
useEffect
を呼び出すことで、DOMへの更新を反映した後に定義した「副作用関数」(今回はdocument.title~の部分)を実行するようにReactに指示しています。副作用は関数コンポーネントのスコープ内に存在するので、state
やprops
にアクセスできます。デフォルトでは初回のレンダーも含む毎回のレンダー時にこの副作用関数が呼び出されます。
また、useState
のように、1つのコンポーネント内に2つ以上の副作用を使用することができます。
このフックを利用することで、クラスコンポーネント時代でのライフサイクルメソッドのように、分離して書かざるを得なかったコンポーネント内の副作用を、関連する部分同士で整理して記載することが可能になしました。
副作用の実行タイミング
後でも記載しますが、マウント時を含む依存配列の値の更新時に実行する、よりも初回のレンダー時も含む毎回のレンダー時に実行すると理解しておいた方が頭が混乱しないと思います
useEffect(深堀)
useEffect(didUpdate);
useEffect
は副作用を有する可能性のある命令型のコードを受け付けます。渡された関数はレンダーの結果が画面に反映された後に動作します。
デフォルトでは、レンダー終了した後に副作用関数が毎回動作しますが、特定の値が変化した時のみ動作させるようにすることもできます。
関数コンポーネント(Reactのレンダーフェーズ)での、DOMの書き換え、データの購読、タイマー、ロギング、あるいはその他の副作用を記述することはできません。UIにまつわるややこしいバグや非整合性を引き起こす原因になります。
エフェクトのクリーアップ
副作用はしばしば、コンポーネントが画面から消える場合にクリーンアップする必要があるようなリソース(購読やタイマーIDなど)作成します。useEffect
に渡す関数はクリーンアップ用関数を返すことができます。
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
});
メモリリークを防止するために、コンポーネントがUIから削除される前にクリーンアップ関数が呼び出されます。加えて、コンポーネントが複数回レンダーされる場合、新しい副作用を実行する前に前回の副作用はクリーンアップされます。
この例は、更新が発生するたびに新しい購読が作成される、ということです。
副作用のタイミング
componentDidMount
やcomponentDidUpdate
とは異なり、useEffect
は渡された関数はレイアウトと描画の後で遅延されたイベントとして実行されます。ほとんどの作業はブラウザによる画面への描画を妨げるべきではないので、購読やイベントハンドラのセットアップなどの副作用にとっては、この遅延のイベント処理は適切です。
useLayoutEffect
useEffect
とは実行タイミングが異なり、ユーザが見えるようなDOMの改変のような、ユーザが見た目の不整合性を感じずに済むように、次回の描画が発生する前に同期的に発生させたい場合に使用します。
条件付きで副作用を実行する
デフォルトの動作では、副作用関数はレンダーの完了時に毎回実行されます。これにより、コンポーネントの依存外列のいずれかが変化した場合に毎回副作用が再作成されます。
しかし、これがやりすぎなパターンがあります。例えば、新しい購読を設定する必要がある場合、毎回の更新ではなくsource
プロパティが変化したときのみです。
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},[props.source];
);
useEffect
を使うことで、props.source
の値が変更された場合の再作成されるようになります。
まとめ
-
useEffectは何をやっているの?
このフックを、レンダー後に何かの処理をしないといけない、ということをReactに伝達します。Reactは定義した副作用関数を記憶し、DOMの更新の後に呼び出して実行します。 -
useEffectがなぜコンポーネント内で呼ばれるのか?
コンポーネント内に記述することで、副作用内からstate
やprops
にアクセスすることができるようになります。これはすでに関数スコープ内に存在するので、参照するために特別なAPIは必要ありません。JavaScriptのクロージャを活用しているため、React特有のAPIを導入することはありません。 -
useEffectは毎回のレンダー後に呼ばれるのか?
デフォルトでは、副作用関数は初回のレンダー時及び
毎回の更新時に呼び出されます。「マウント」と「更新」の観点ではなく、「レンダーの後」に副作用は起こる、というように考える方が簡単かもしれません。
次回は、レンダリングの最適化について記述したいと思います。