Component の種類
React でコンポーネントを定義する際は大きく分けてクラスコンポーネント、関数コンポーネントの 2 つに分けることができます。
これらのコンポーネントについては、コンポーネントと props についてで説明していますので、そちらもセットで見ると理解が深まると思います。
Hooksの導入以前(React 16.8 以前)の React では、関数コンポーネントに state を持たせることができなかったので、state を持たせるコンポーネントを実装場合はクラスコンポーネントを実装するしかありませんでした。
しかし、Hooksの導入により、関数コンポーネントで State を管理することができるようになり、公式からも Hooks を使うことが推奨されています
https://ja.reactjs.org/docs/hooks-faq.html#do-i-need-to-rewrite-all-my-class-components
ではこれから、Hooks を用いて関数コンポーネントを使うべき理由を挙げていきます。
理由 1 : this を使う必要がなくなる
JavaScript におけるthis
は他の言語と違って少し複雑ですが、クラスコンポーネントを使うと state を参照する際にthis
を用いる必要があります。しかし、関数コンポーネントで Hooks を用いると this は全く使う必要がなくなり、コードもシンプルになります。
以下にクラスコンポーネントの例と関数コンポーネントの例を示します。
- クラスコンポーネントの例
import React from "react";
class Example extends React.Component {
constructor(props) {
super(props);
// thisを用いてstateを参照しなくてはならない
this.state = { count: 0 };
this.handleCount = this.handleCount.bind(this);
}
handleClick() {
// stateをセットする関数、stateにthisで参照する必要
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
export default Example;
- 関数コンポーネントの例
import React, { useState } from "react";
const Example = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
// Hooksを用いているのでthisを使わなくてもstateを参照できる
setCount(() => count + 1);
};
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>+1</button>
</div>
);
};
export default Example;
クラスコンポーネントの場合は、state を参照する時やメソッドを用いる時にthis
を使う必要があり、コードも長くなり、開発者を困惑させる可能性もあります。
一方で関数コンポーネントで Hooks を利用すると(この例だと useState)、state やメソッドに this なしで参照することができて、コードの可読性が上がります。
理由 2 : メソッドを bind する必要がなくなる
先ほどの例では、ボタンをクリックした際にクラス内のメソッドであるhandleClick
が呼び出されるようになっています。
クラスコンポーネントではメソッドを利用するにはコンストラクタ内でコンポーネントの this に bind する必要があります。
this.handleCount = this.handleCount.bind(this);
また、クラスコンポーネントでも bind せずにアロー関数でメソッドを書き直すこともできます。
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
しかし、関数コンポーネントではこれらのことに気を遣う必要はありません。bind せずとも、先ほどの例のようにシンプルにメソッドを使うことができます。
理由 3 : constructor, render が不要になる
これも理由 1 の例を見ると、クラスコンポーネントではconstructor
で state の初期化、props の継承、メソッドの bind を行っていることがわかります。
それに対して、関数コンポーネントではuseState
を用いて state を初期化しており、props は関数の引数として受け取っているので constructor に比べて記述量が減ります。
また、単純に render メソッドを関数コンポーネントは記述する必要がないため、記述量が減るメリットがあります。
理由 4 : ライフサイクルメソッドに分割する必要がないため同一ロジックが混入しにくい
クラスコンポーネントでは、componentDidMount
, componentDidUpdate
, componentWillUnmound
などのライフサイクルメソッドが存在します。それらはしばしばコンポーネントの規模が大きくなるにつれて、数カ所のライフサイクルメソッドで同一ロジックが記述されてしまうことがあります。
例えば、コンポーネントのマウント時(componentDidMount)と更新時(componentDidUpdate)に同一のロジックが書かれているというケースはよくあるかと思います。
これを Hooks を用いることで、ライフルサイクルメソッドによって分割せずに、関連する機能に基づいて、コンポーネントを分割することができます。これにより、同じロジックが混入しにくくなります。
詳細 : https://ja.reactjs.org/docs/hooks-effect.html
理由 5 : コンポーネント間でステートフルロジックを共有することが容易
クラスコンポーネントでは、コンポーネント間でステートフルロジックを共有するには、レンダープロップや高階コンポーネントを実装する必要があり、これらはコンポーネントを再構築する必要があり、いくらかコストがかかかります。
そこで関数コンポーネント内でカスタムフックを使用することで、再利用可能なステートフルロジックを抽出・テストすることができます。
例を挙げます。以下はカスタムフックを抽出する前の関数コンポーネントです。
export const Timer = () => {
const [time, setTime] = useState("");
// 以下のロジックを共有したい
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, [setTime]);
return <div>{time}</div>;
};
上記の、Hooks 周りのロジックをカスタムフックとして抽出します
const useTime = milliSeconds => {
const [time, setTime] = useState("");
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, milliSeconds);
return () => clearInterval(timer);
}, [setTime]);
return time;
};
カスタムフックを利用することで、最初に示した関数コンポーネントと同じコンポーネントを実現できます。
export const Timer = () => {
const time = useTime(1000);
return <div>{time}</div>;
};
他のコンポーネントでもカスタムフックを使うだけでロジックの共有ができます。
export const SecondTimer = () => {
const time = useTime(5000);
return <div>{time}</div>;
};
まとめ
今回は、React でクラスコンポーネントより関数コンポーネントを使うべき特に重要な理由について説明しました。
React でこれからアプリケーション開発を行う方は、是非 Hooks を関数コンポーネントで使ってみてください 🙌