はじめに
最近Reactを勉強中です。
useEffectについて学んだことを残します。
そもそもReactコンポーネントがどうあるべきか
useEffectの使いどころを理解するために、Reactコンポーネントがどうあるべきかをまとめます。
以下の公式ドキュメントを参照しました。
https://ja.react.dev/learn/keeping-components-pure
ドキュメントの冒頭には以下のような記載があります。
コンポーネントを常に厳密に純関数として書くことで、コードベースが成長するにつれて起きがちな、あらゆる種類の不可解なバグ、予測不可能な挙動を回避することができます。
また、以下のようにも書かれていました。
React は、あなたが書くすべてのコンポーネントが純関数であると仮定しています。つまり、あなたが書く React コンポーネントは、与えられた入力が同じであれば、常に同じ JSX を返す必要があります。
Reactは、コンポーネントは常に純粋関数であるという前提で作られているようです。
純粋関数とは、以下の特徴があります。
- 同じ入力に対しては常に同じ結果を返す
- 引数のみを参照し、外部で宣言された変数を読み書きしない(状態を変更しない)
例えば、以下の例は、Counter
コンポーネントの外部の count
を変数を読み書きしています。
let count = 0; // 外部の変数(グローバル変数)
function Counter() {
count++; // 外部の変数を変更(副作用)
console.log(`Count: ${count}`);
return (
<div>
<p>Count: {count}</p>
</div>
);
}
export default Counter;
これにより、複数の Counter
コンポーネントを使った場合、 count
が共有され、予期しない動作になることが考えられます。
通常、各 Counter
コンポーネントは独立して動作し、それぞれの count
が個別にカウントアップされることを期待すると思います。
Counter 1: 0 → 1
Counter 2: 0 → 1
しかし、グローバル変数 count
を共有しているため、すべての Counter
が同じ count
を参照 するため以下のようになります。
Counter 1 → count が増加 (0 → 1)
Counter 2 → count はグローバルなので、Counter 1 と共有されてしまう (1 → 2)
このように、純粋関数でなくなることで、どのような結果が返ってくるかが予測不可能になり、バグを生みやすいコードになります。
また、このような、計算する過程でシステムの状態に変更を加えたり外部との入出力を行うことは、関数の主な作用である計算結果を計算する以外の作用であることから副作用と呼ばれます。
ファイルシステムの変更、HTTP呼び出し、状態変更、ログ出力、DOMの取得なども副作用に含まれます。
どんな時に使うものか
公式ドキュメントに以下のように記載がありました。
React では、副作用は通常、イベントハンドラの中に属します。イベントハンドラは、ボタンがクリックされたといった何らかのアクションが実行されたときに React が実行する関数です。イベントハンドラは、コンポーネントの「内側」で定義されているものではありますが、レンダーの「最中」に実行されるわけではありません! つまり、イベントハンドラは純粋である必要はありません。
いろいろ探してもあなたの副作用を書くのに適切なイベントハンドラがどうしても見つからない場合は、コンポーネントから返された JSX に useEffect 呼び出しを付加することで副作用を付随させることも可能です。これにより React に、その関数をレンダーの後(その時点なら副作用が許されます)で呼ぶように指示できます。ただしこれは最終手段であるべきです。
イベントハンドラもしくはuseEffect
を使用することで、関数コンポーネントを純粋に保ちつつ副作用を処理できる、レンダリングの最中に副作用処理をすることを避けられ、上記の予期しない動作を防げるということのようです。
イベントハンドラではユーザーのクリック、ホバーなどのアクションに応じてプログラム変更できますが、
useEffect
は、特定のアクションではなく、コンポーネントが描画(レンダリング)されたタイミングで実行する処理を指定するために使います。さらに、依存関係を指定することで、特定の状態やプロパティが変更されたときに実行することもできます。
例えば、HTTP呼び出しは副作用なので、コンポーネントが表示されることとは関係なく行われるべき処理です。
useEffectを使用すると、画面が更新される「描画」の後にその処理を実行することを指定できるので、Reactコンポーネントと外部システム(ネットワークやサードパーティのライブラリなど)をうまく同期させることができます。
ここまででお腹いっぱいなので、分けます。
続きは以下。
useEffectについて理解する②
参考
https://ja.react.dev/learn/keeping-components-pure
https://ja.react.dev/learn/synchronizing-with-effects