🎉

React初心者向け🔰SPA開発ガイド

2023/08/09に公開

はじめに

友人向けにReactの勉強会をすることになったので、せっかくなら記事にしてしまおうということで記事にします。
かなり噛み砕いて書きますので、多少の誤謬はお許しください。コメントにてご指摘いただけますと幸いです。
Next.jsは用いない記事となりますので、Next.jsの記事を求めている方向けではないかもしれません。

Reactとは

Reactは、Meta(旧Facebook)社が開発したJavaScriptライブラリで、ウェブ上のユーザーインターフェイスを構築するために使用されます。シングルページアプリケーション(SPA)の開発に特に人気で、コンポーネントベースの開発が可能です。

特徴

コンポーネントベース

Reactでは、コンポーネントという再利用可能な部品を組み合わせて、複雑なUIを構築します。

仮想DOM

Reactは、実際のDOMとは別に仮想DOMを持ち、変更があるときだけ実際のDOMを更新します。これによって、パフォーマンスが向上します。
JavaScriptやjQueryなどのフレームワークでは、スクリプトをもとにHTMLを再作成し、それを元にブラウザに表示するための描画を行います。このため、表示が遅くなってしまいます。
一方でReactは仮想DOMを構築します。これは実DOMとは別の仮想的なDOMです。このためスクリプトから情報を受け取ってもすぐにはブラウザの描画を行わず、仮想DOMを構築します。その後、構築された仮想DOMの内容を元にHTMLを作成し、実DOM(リアルDOM)に同期します。
この際は差分を反映します。

JavaScript(TypeScript)との統合

JSX(TSX)というJavaScriptに似た構文を使用して、JavaScript内でHTMLを簡単に扱えるようになっています。

TypeScriptとの相性

Reactは近年人気のフレームワークであるVueよりもTypeScriptとの相性がいいとされています。
TypeScriptは、Microsoftによって開発されたプログラミング言語で、JavaScriptに静的型付けとクラスベースのオブジェクト指向を追加しています。TypeScriptはJavaScriptのスーパーセットであるため、JavaScriptコードはそのままTypeScriptとして機能します。型システムのおかげで、コードの品質を向上させることができます。
ReactがTypeScriptと相性がいいとされている理由は以下のようなものがあります。

  1. 公式サポート
    Reactは、初期からTypeScriptとの統合を強く意識して開発されています。TypeScriptの型定義ファイルはReactの公式リポジトリに保管されており、Reactの更新と同時に型定義も更新されます。

  2. コミュニティのサポート
    Reactのコミュニティは非常に広く、多くの開発者がTypeScriptを使用しています。その結果、TypeScriptでReactを使用するためのガイドやチュートリアル、サードパーティライブラリの型定義などが充実しています。

  3. 型定義の簡潔さ
    Reactのコンポーネントは、関数またはクラスとして定義されるため、TypeScriptの型システムと自然に組み合わせることができます。以下は、Reactのコンポーネントでの型定義の一例です。

公式ガイド

実装

コンポーネント

Reactにはクラスコンポーネントと関数コンポーネントがある。
以前はクラスコンポーネントが主流でしたが、現在は関数コンポーネントが主流です。

関数コンポーネントが主流な理由

  1. Hooksの導入
    React 16.8でのHooksの導入によって、関数コンポーネントの人気が高まりました。Hooksを使うことで、以前はクラスコンポーネントでしか使用できなかった機能(例: 状態管理、ライフサイクルメソッド)を、関数コンポーネント内で使うことができるようになりました。

  2. TypeScriptとの相性
    関数コンポーネントは、TypeScriptとの相性も非常に良いです。関数コンポーネントの型推論はクラスコンポーネントよりも直感的で、エディタのサポートも豊富です。

  3. 公式のドキュメント
    Reactコミュニティと公式ドキュメントでも、新しいコンポーネントの作成には関数コンポーネントとHooksの使用が推奨されています。このような公式のガイドラインも、関数コンポーネントの普及に寄与しています。

実装例

クラスコンポーネント

import React, { Component } from 'react';

type MessageClassComponentProps = {
  message: string;
};

class MessageClassComponent extends Component<MessageClassComponentProps> {
  render() {
    return (
      <div>
        <h1>{this.props.message}</h1>
      </div>
    );
  }
}

// 使用例
// <MessageClassComponent message="Hello from Class Component!" />

関数コンポーネント

import React from 'react';

type MessageFunctionComponentProps = {
  message: string;
};

const MessageFunctionComponent: React.FC<MessageFunctionComponentProps> = (props) => {
  return (
    <div>
      <h1>{props.message}</h1>
    </div>
  );
};

// 使用例
// <MessageFunctionComponent message="Hello from Function Component!" />

公式Hooks

HooksはReact16.8から誕生しました。誕生には以下のような背景があります。

  1. ロジックの再利用
    以前は、コンポーネント間でロジックを共有するために、高階コンポーネント(HOC)やレンダープロップなどのパターンが使われていました。(気になったら調べてみてください。)これらの方法は便利でしたが、しばしばコードの理解とメンテナンスを困難にしました。Hooksは、関連するロジックを一箇所にまとめることができるため、コードの再利用と組み合わせがより簡単になりました。
  2. 関数コンポーネントの理解のしやすさ
    クラスコンポーネントはJavaScriptのクラスの理解を必要とし、thisの挙動や、ライフサイクルメソッド内でのロジックの分散など、初心者にとって難しい部分がありました。Hooksを使うと、関数だけでコンポーネントを構築できるようになり、多くの開発者にとって理解しやすいコードになりました。

useState

useStateはローカルステートを管理するためのHooksです。
ローカルステートとは、特定のコンポーネント内で管理され、そのコンポーネントの挙動や表示を制御するデータのことを指します。ローカルステートは、そのコンポーネント自体と、必要に応じてその子コンポーネントでのみアクセス可能です。他のコンポーネントからは直接アクセスできません。

使用方法

useStateは次のように使用します。

const [state, setState] = useState(initialValue);

state: 現在のステートの値です。
setState: ステートを更新するための関数です。
initialValue: ステートの初期値です。

実装例

よくある簡単なカウンターの実装例です。

import React, { useState } from 'react';

export const Counter: React.FC = () => {
  const [count, setCount] = useState<number>(0); // 初期値を0に設定し、型を<number>としています。

  const increment = () => {
    setCount(count + 1); // カウントを1増やす
  };

  return (
    <div>
      <p>Count: {count}</p> {/* 現在のカウントを表示 */}
      <button onClick={increment}>Increment</button> {/* ボタンクリックでincrement関数を実行 */}
    </div>
  );
};

useEffect

useEffectは、副作用(データの取得、サブスクリプションの設定、手動でのReactコンポーネントのDOMの変更など)をコンポーネント内で行うためのHookです。

useEffectは、以下の2つのタイミングで実行されます。

  1. コンポーネントが描画された後(マウント時)
  2. 依存配列に指定された値が変更された後

使用方法

useEffectは次のように使用します。

useEffect(() => {
  // 副作用のコード
  return () => {
    // クリーンアップのコード(オプショナル)
  };
}, [dependencies]);

最初の引数は副作用を実行する関数です。(()=> { ... }の部分)
オプショナルなクリーンアップ関数は、コンポーネントがアンマウントされる際や、依存配列の値が変更される前に実行されます。
第二引数は依存配列です。指定した値に変更があった場合にのみ副作用が再実行されるようにするためのものです。dependenciesが変更されるとuseEffectの第一引数の関数が再実行されます。
依存配列が空の場合、副作用はコンポーネントがマウントされた直後に1回だけ実行されます。これは、APIからデータを取得してページに初期値を表示する際など、コンポーネントがマウントされた際に一度だけ何かを実行したいケースでよく使用されます。

実装例

次のコンポーネントは、画面の幅が変更されるたびにその値を表示する例です。

import React, { useState, useEffect } from 'react';

export const WindowWidth: React.FC = () => {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 依存配列が空なので、副作用はマウントとアンマウント時にのみ実行されます。

  return (
    <div>
      <p>Window width: {width}</p>
    </div>
  );
};

依存配列は空なので、初回マウント時に実行されます。

 window.addEventListener('resize', handleResize);

で画面のリサイズに対するイベントリスナーを登録し、リサイズされるとhandleResizeが実行されるようになっています。

    const handleResize = () => {
      setWidth(window.innerWidth);
    };

widthステートをセットして画面上で幅を表示しています。

    return () => {
      window.removeEventListener('resize', handleResize);
    };

はコンポーネントがアンマウントされる(削除されたり)するときに実行されるクリーンアップ関数です。画面のリサイズに対するイベントリスナーが不要になるので、きちんと削除しています。
不要なイベントリスナーが残ってメモリリークなどの問題が起きることを防いでいます。

各種ライブラリ

すべてyarnで書くのでnpmを使う場合は公式ドキュメントを参照してください。

状態管理

グローバルステート(ローカルステートとは異なり子以外のコンポーネント間で共有できる状態)を管理できるライブラリとしてはRecoilが有名です。
RecoilはMetaが開発したReact用のステート管理ライブラリで、アプリケーション全体で状態を簡単に共有するために使用されます。

公式ドキュメント

公式ドキュメント

導入

インストール

yarn add recoil

RecoilRootをプロジェクトに追加する

RecoilRootコンポーネントは、アプリケーションの最上位に配置する必要があります。これによって、子孫のコンポーネントがRecoilの状態にアクセスできるようになります。

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import { RecoilRoot } from 'recoil'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </React.StrictMode>,
)

Recoilの状態(アトム)を作成する

Recoilのアトムは共有状態の最小単位で、他のコンポーネントで読み取りや更新ができます。
カウントの例

import { atom } from 'recoil';

export const countState = atom({
  key: 'countState', // ユニークな名前
  default: 0, // デフォルト値
});

ルーティング

react-router-dom

Reactのプロジェクトでルーティングを取り扱うための非常に人気のあるライブラリです。
プロジェクト内でインストールされていない場合は以下でインストールできます。

yarn add react-router-dom

BrowserRouterをプロジェクトに追加する

BrowserRouterコンポーネントをアプリケーションの最上位に配置する必要があります。

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import { RecoilRoot } from 'recoil'
import './index.css'
import { BrowserRouter } from 'react-router-dom'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RecoilRoot>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </RecoilRoot>
  </React.StrictMode>,
)

ルーティングの設定

以下のように設定することで、指定したパスでelementで指定したページを表示することができます。

import { Route, Routes } from 'react-router-dom';
import { TestPage } from './pages/Test/TestPage';
function App() {
  return (
    <div className="App">
      <Routes>
        <Route path='/' element={<TestPage />} />
      </Routes>
    </div>
  );
}

export default App;

ビルドツール

Vite

高速で動作するビルドツールです。
HMR(Hot Module Replacement)がついているのでサクサク開発できます。
npx create-react-appでReactプロジェクトを作成するより、yarn create viteでTypeScript指定してプロジェクトを作成する方がおすすめです。(対話形式で色々設定できるので初心者にとっても良さそう)]

公式ドキュメント

公式ドキュメント

プロジェクトの開始

プロジェクトの作成

yarn create vite

色々聞かれますが、アプリ名を指定して、ReactでTypeScriptを選択すればあとはお好みでいいです。

依存関係の初期化

cd アプリ名
yarn

devサーバーの起動

yarn dev

終わりに

内容は適宜追加していきます。

Discussion