はじめに

一年前にReactを使用してアプリ作成や学習を行い転職活動をしていたが、当時の僕はプログラミングやアプリケーションは動けばいいくらいのトンチンカンでまともにドキュメント読み込む力もありませんでした。
そして現在、エンジニア一年生として改めてReactの中で気になっているクラスコンポーネントと関数コンポーネントの違いやライフサイクル、状態管理、React HooksなどといったReact基礎について理解したく今回まとめてみました。
クラスコンポーネントと関数コンポーネントの違い
まず結論から言うとpropsやstate、ライフサイクルの記述方法が違う点がありました。
(React Hooksが登場してからです。※React 16.8.0版リリース:2019/2/6)
まず軽く前提としてUIを構築するのにコンポーネントという概念と、親から子コンポーネント間でデータを渡すpropsという概念があります。
詳しくはドキュメントのコンポーネントとpropsに記載してあります。
そしてコンポーネントを作成する際には、
- クラスコンポーネント
- 関数コンポーネント
の二つが存在します。
先程のpropsやstate、ライフサイクルの記述方法が違う点は、クラスコンポーネントでしか行えなかったstateやライフサイクル機能がReact Hooksが登場して以来、関数コンポーネントでの使用が可能になりました。
それまで関数コンポーネントの役割としてはViewを表示するための役割で、ステート管理している親コンポーネントからpropsとしてデータを受け取るステートレスとしてのコンポーネントでした。
実際にコード別で簡単にクラスコンポーネントと関数コンポーネントを比較してみました。
クラスコンポーネント
import React from "react";
class ClassComponent extends React.Component {
render() {
return <h1>Hello, world</h1>;
}
}
クラスコンポーネントを作成するのに、React.Component
を継承し必ずrender()
を定義する必要があります。
render()
については、ライフサイクルの箇所で説明します。
関数コンポーネント
import React from "react";
function FunctionalComponent() {
return <h1>Hello, world</h1>;
}
関数コンポーネントに関しては、関数の中でJSXを記述することでコンポーネントの作成ができます。
state(状態)管理
コンポーネントを利用する時に設定できるStateという値があります。
Reactのstateや状態管理としては、主にRedux, useState, useContextなど様々なやり方があります。
useState
useStateは、関数コンポーネントでのstate管理・更新をすることができます。
const [値, 更新する値] = useState(初期値)
として扱うことができます。
useStateを使用して**親コンポーネントで値(state)**をもち、子コンポーネントでpropsで値を受け取るといった伝達が鉄板ですね。
しかし、規模が大きくなるにつれてコンポーネントツリーの階層が多くなりpropsのバケツリレーの連続で管理のメンテナンスやパフォーマンスが疎かになります。
そういった時に役立つのが、先程説明したReduxや次のuseContextを使用することでstate管理を円滑にすることができます。
useContext
コンポーネントツリー内にpropsを経由せずとも値を渡すやり方がコンテキストと呼ばれる方法があります。主に多くのコンポーネントで扱う値をグローバルステート管理したい時に使用します。
注意したいのが、管理しているコンテキストの値の変や(コンテキストを設定した)コンポーネントがレンダリングすると渡したコンテキスト値も変更するので出来るだけシンプルな管理が必要になります。
useContextを使用する場合は以下が必要です。
-
createContext()
オブジェクトのデフォルト値を引数に設定。 -
Provider
providerで包みvalue値を決めることでコンテキストを設定します。 -
useContext()
引数にコンテキストを渡すことで使用可能。
const themes = {
light: {
background: "#eeeeee"
},
dark: {
background: "#222222"
}
};
// コンテキストオブジェクトの設定
const ThemeContext = React.createContext(themes.light);
// 親コンポーネント
function App() {
return (
// Providerでコンポーネントを包み、valueの値を決めて設定
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 子コンポーネント
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
// 子孫コンポーネント
function ThemedButton() {
// useContextの引数にコンテキストオブジェクトを設定することで使用可能。
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background }}>
I am styled by theme context!
</button>
);
クラスコンポーネントの場合は、
ちなみにクラスコンポーネントではconstructor
内で、state
の管理を行い、setState()
でstateを更新することができます。
詳しくは、コンポーネントのstateに記載してあります。
Redux
状態管理ライブラリと呼ばれており、本来¥コンポーネントでのstate管理を行いますがReduxではStoreを使用しコンポーネントを切り離してstate管理を行います。扱うデータが多い中規模・大規模での使用に特化しております。
reduxの内容をとてもわかりやすくまとめた記事がありました。
https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7
まだまだやり方たくさんある。。。
主に3つの状態管理を挙げましたが他にもReact HooksのuseReducer、GrahpQL利用時にはApollo Clientを使用するケースや状態管理ライブラリであるreduxと類似したRecoleやMobXなど、Reactには様々やり方があってサービスの規模感に応じて技術選定が必要になるので一年ぶりに凄みを感じました。
ライフサイクルとは
まず簡単にライフサイクルとは、コンポーネントが生まれてから死ぬまでをサイクルとするように、
生成(Mounting)➡︎ 更新(Updating)➡︎ 削除(Unmounting)の繰り返すことを言います。
主に使用されているライフサイクルメソッドは、以下になります。
📌 Mounting
-
constructor()
マウントされる前に呼び出されるメソッドで、ローカルstateの初期化やイベントのバインドを行う。 -
render()
仮想DOMを構築するメソッド。もちろんコンポーネントに必須でもある。 -
componentDidMount()
最初のrender()
が呼びだされる(マウント直後)メソッドで、ネットワークの通信処理に利用されることがある。
📌 Updating
-
render()
上記の内容と同じ。 -
componentDidUpdate()
更新が完了したタイミングのメソッド。更新完了後のネットワークリクエストの通信時に利用する。
📌 Unmounting
-
componentWillUnmount()
コンポーネントが破棄されるタイミング(アンマウント)のメソッド。ネットワークリクエストのキャンセルや作成したデータの加工(削除)に使用。
以上で説明したライフサイクルメソッドは、クラスコンポーネントでの使用になります。
ちなみに関数コンポーネントの場合はReact HooksのuseEffect
を使用して、ライフサイクルを行うことができます。
React Hooks useEffect版のライフサイクル
React HooksのuseEffectは、先程の上記で紹介したライフサイクルメソッドであるcomponentDidMount()
, componentDidUpdate()
, componentWillUnmount()
を一つにまとめたメソッドです。
なので、useEfftctを使用することでライフサイクルを作成することができます。
useEfftctの使い方
useEffect(第一引数:()=> { 処理 }, 第二引数:[依存変数])
として記述します。
- 第一引数のコールバック関数はレンダリング時に呼ばれ副作用の処理を行います。また、その処理の中で戻り値を関数としたものクリーンアップ関数と呼ばれてコンポーネントがアンマウントされた時に実行されます。
- 第二引数は、省略することもできます。配列の中身に依存させる変数を格納することで、変数が変更された場合に実行される仕組みになっております。
※ここでいう副作用とはレンダリング時に関係のない処理のことを指しております。
import React, { useEffect, useState } from "react";
const Example = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count);
}, 2000);
// クリーンアップ関数
return () => {
clearInterval(id);
};
}, [count]); // countが依存変数
return <button onClick={() => setCount(count + 1)}>Click me</button>;
};
export default Example;
上記の内容は、レンダリング時にcount
の値がコンソールログに2秒後に表示➡︎count
がプラスされるボタンをクリックすると再レンダリングされ2秒後にcountがプラスされているコンソールログが表示といった流れです。そして、コンポーネント破棄(アンマウント)されたらcount
を解除するといったクリーンアップ関数が実行されます。
まとめ
- Reactのコンポーネント作成は、クラスコンポーネントと関数コンポーネントの概念がありstateやライフサイクルの記述がそれぞれある。
- state管理においては、useState, useContextなどのHooksやReduxなど様々あり規模感に応じて使い分ける。
- ライフサイクルはMounting ➡︎ Updating ➡︎ Unmountingのサイクル。