はじめに
5日間でReactの基礎を習得する試みの3日目です。3日目は、フックについて学びます。
私はC#やPythonを仕事で使っているため、オブジェクト指向の言語的な考え方や、Pythonでの例えを使って理解を進めます。同じような境遇の方の理解の助けになれば幸いです。
フック(hook)
クラスコンポーネントでは出来るが、関数コンポーネントでは出来ないような実装があります。
例えば、クラスコンポーネントはメンバ変数に値を保持しておくことができます。しかし、当然ながら関数コンポーネントではstateなどのメンバ変数を保持することができません。
そこでフックを使う
メンバ変数等が必要でクラスコンポーネントでしかできなかった実装を、関数コンポーネントできるようにしてくれるのがフックです。
ここでは特にメジャーなuseStateとuseEffectを扱います。
useState
useStateは関数コンポーネントで、stateを使うためのフックです。(stateは2日目に扱いました。)
使ってみる
1日目に作ったプロジェクトのsrc/app/page.jsには、Homeコンポーネントが定義されています。
この中身を全部消して、以下のように編集しましょう。
"use client";
import React, { useState } from 'react';
// ボタンをクリックすると、表示される値がインクリメントされるコンポーネント
export default function Home() {
// useStateを使用してカウンターの状態を初期化
const [clickedCount, setClickedCount] = useState(0);
// カウンターをインクリメントする関数
const incrementCount = () => {
setClickedCount(clickedCount + 1);
};
return (
<div>
<h1>Welcome to Next.js!</h1>
<p>クリック数のカウント: {clickedCount}</p>
<button onClick={incrementCount}>Increment Counter</button>
</div>
);
}
npm run dev
で起動すると、画面が表示されます。
"Increment Counter"ボタンを4回クリックすると次のような画面になります。
解説
useStateは const [stateを格納する変数名, stateを更新する関数名] = useState(stateの初期値);
で宣言します。
const [clickedCount, setClickedCount] = useState(0); // clickedCountの初期値は0になる
ボタンをクリックしたときのイベントにincrementCount関数を設定しています。この関数内でsetClickedCount(clickedCountの値を更新する関数)を実行しています。
// カウンターをインクリメントする関数
const incrementCount = () => {
setClickedCount(clickedCount + 1);
};
incrementCountが実行されると、clickedCountの値が変更されるため、再描画されて"クリック数のカウント: "の数値が更新されます。
// clickedCountの値が変わると、画面が再描画されて値が更新される
<p>クリック数のカウント: {clickedCount}</p>
useEffect
useEffectは、関数コンポーネントでコンポーネントが描画された後に副作用を実行するためのフックです。
副作用とは、コンポーネントのUIを変えるような処理・外部に対して操作する処理のことを指します。具体的な内容はこちら の記事が参考になります。
useEffectのクラスコンポーネントからの理解
副作用は、コンポーネントがすべて表示(マウント)されたあとに実行します。 クラスコンポーネントを使えば、コンポーネントが描画されたときの初期処理、コンポーネントが更新された後の処理などに、任意の処理を実行させられます。これは、各処理に相当するメソッドがクラスコンポーネントが継承しているReact.Copmponentに実装されており、それをoverrideすれば実装できます。// これは模擬的な React.Component クラス。この感じのものをクラスコンポーネントは継承している。
class React.Component {
constructor(props) {
this.props = props;
}
componentDidMount() {
// コンポーネントマウント後の初期処理
}
shouldComponentUpdate(nextProps, nextState) {
// プロップス/ステート変更時の再レンダリング判断
return true;
}
componentDidUpdate(prevProps, prevState) {
// コンポーネント更新後の処理
}
componentWillUnmount() {
// コンポーネントアンマウント前のクリーンアップ
}
render() {
// UIレンダリングのロジックを実装必須
throw new Error("Render method must be implemented");
}
}
しかし、関数コンポーネントでは、このようなoverrideはできません。そこで、useEffectフックを使います。
使ってみる
Homeコンポーネントを以下のように編集しましょう。
"use client";
import React, { useEffect } from 'react';
// マウント(画面表示)完了後にコンソールにログが出力されるコンポーネント
export default function Home() {
// コンポーネントがマウントされた後の副作用
useEffect(() => {
console.log('Component is mounted.');
}, []);
return (
<div>
<h1>Welcome to Next.js!</h1>
<p>コンソールをチェックしてください</p>
</div>
);
}
npm run dev
で起動したあと、ブラウザで表示してF12の開発者ツールにてコンソールを確認してください。
https://qiita.com/kim_t0814/items/ce95b6b92e7994d4fdac#react18%E3%81%AE%E6%8C%99%E5%8B%95 この記事によると、開発モードで実行すると、2回マウントされるようなので、コンソールにも2回分のログが出ています。
解説
useEffectは、useEffect(実行する関数, 監視する値のリスト[省略可])
で宣言します。
2引数目の"監視する値のリスト"ですが、このリストに値を入れておくと、その値が変わるたびに、関数が実行されます。何も書かない場合([]とする場合)、コンポーネントがマウントされる時だけコードが実行されます。
useEffect(() => {
console.log('Component is mounted.');
}, []); // 今回はマウント時だけ実行されることになる
最後に
フックは他にも沢山ありますし、自作もできるようです。ただし、大事なことは"クラスコンポーネントでしかできなかった実装を関数コンポーネントでもできるようにする"という基本を覚えておくことだと感じました。
複雑な場合は、クラスコンポーネントだったらどう実装するのか、という観点から理解するのがよさそうです。
次回は既存のリポジトリを編集し、実践的に学びます。
何か間違いがあれば、是非コメントを頂けると助かります!