Reactで簡単なアプリケーションを作成する

Reactを使って簡単なアプリケーションを作成したいと思います。
画面のイメージとしては、以下のような感じです。

ホームページ

ページ1

ページ2

ページ3

ちなみに管理人はReact初心者です。
Reactのバージョンの差異などが原因で解説サイトのサンプルコードが動作しなかったりして苦労したので、今後自分がReactアプリケーションを作成するときに参考になるように、備忘録としてここに残しておこうと思います。
間違いなどあれば、お気軽に連絡していただければと思います。

最終的なディレクトリ構造は以下のとおりです。

.
├── package-lock.json
├── package.json
├── src
│   ├── index.html
│   └── js
│       ├── App.js
│       ├── components
│       │   └── NavBar.js
│       ├── index.js
│       └── pages
│           ├── Home.js
│           ├── Page1.js
│           ├── Page2.js
│           └── Page3.js
└── webpack.config.js

管理人の開発環境は以下のとおりです。

  • Windows 11
  • Node.js v18.15.0

この記事の執筆にあたっては、以下のQiita記事を参考にしました。

プロジェクトの作成

自分はnpm initしてからnpm install --save-dev ...で必要なパッケージをインストールしましたが、具体的なコマンドを忘れてしまったのと、新しくnpm install --save-dev ...すると自分の環境とパッケージのバージョンが変わっていて動作しなくなったりしそうなので、自分の手元にあるpackage.jsonを共有します。

{
  "name": "reactsample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve --mode development",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.22.5",
    "@babel/preset-env": "^7.22.5",
    "@babel/preset-react": "^7.22.5",
    "babel-loader": "^9.1.2",
    "prettier": "^2.8.8",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router": "^6.12.1",
    "react-router-dom": "^6.12.1",
    "webpack": "^5.86.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  }
}

package.jsonを作成したら、npm installを実行します。
必要なパッケージがnode_modulesディレクトリ配下に一通りインストールされるはずです。

webpack.config.js

webpack.config.jsはWebpackの設定ファイルです。

Webpackは複数のJavaScriptファイルを一つにまとめる(bundle)ツールです。
開発時にはページやコンポーネントごとにファイルを分けて開発しますが、Webpackを使うとそれらを一つにまとめることができます。
ファイルをまとめることによってブラウザからWebサーバーへのリクエスト数が減少し、パフォーマンスを改善することができます。

const debug=process.env.NODE_ENV!=="production";

const webpack=require("webpack");
const path=require("path");

module.exports={
    context: path.join(__dirname,"src"),
    entry: "./js/index.js",
    module: {
        rules: [{
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            use: [{
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-react","@babel/preset-env"]
                }
            }]
        }]
    },
    output: {
        path: __dirname+"/src/",
        filename: "index.min.js",
        publicPath: "/"
    },
    devServer: {
        static: {
            directory: path.join(__dirname,"src")
        },
        historyApiFallback: true
    },
    plugins: debug?[]:[
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin({mangle:false,sourcemap:false})
    ]
}

src/index.html

トップページです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <meta name="description" content="React sample application" />
    <meta name="author" content="maeda6uiui" />
    <title>React sample application</title>

    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
      crossorigin="anonymous"
    />
  </head>
  <body>
    <!--ここにReactで作成したコンポーネントを表示します-->
    <div id="app"></div>
    <script src="index.min.js"></script>
  </body>
</html>

管理人にはCSSを作成する能力がないので、Bootstrapをそのまま使います。

src/js/index.js

ここがReactアプリケーションのエントリーポイントとなります。

import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";

const app = document.getElementById("app");
const root = ReactDOM.createRoot(app);

root.render(<App />);

先に紹介したindex.htmlで、id="app"をもつdiv要素がありました。
ここでは、そのdiv要素のところにReactで作成したコンポーネントを埋め込む処理を行っています。(たぶん)
実際のアプリケーションはApp.jsで実装していきます。

src/js/App.js

実際のアプリケーションの中身です。
ここではルーティングの設定を行っています。
たとえば、/page-1にアクセスされた場合にはPage1コンポーネントを表示する、といった流れです。
各コンポーネントの具体的な中身はそれぞれ別のファイルで定義しています。

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

import Home from "./pages/Home";
import Page1 from "./pages/Page1";
import Page2 from "./pages/Page2";
import Page3 from "./pages/Page3";

const App = () => {
  return (
    <Router>
      <Routes>
        <Route exact path="/" element={<Home />} />
        <Route path="/page-1" element={<Page1 />} />
        <Route path="/page-2" element={<Page2 />} />
        <Route path="/page-3" element={<Page3 />} />
      </Routes>
    </Router>
  );
};

export default App;

src/js/components/NavBar.js

画面上部に表示されるナビゲーションバーです。

import React from "react";
import { Link } from "react-router-dom";

const NavBar = () => {
  return (
    <nav className="navbar navbar-expand-md navbar-light bg-light">
      <div className="container-fluid">
        <Link to="/" className="navbar-brand">
          Reactサンプル
        </Link>
        <ul className="navbar-nav me-auto">
          <li className="nav-item">
            <Link to="/page-1" className="nav-link">
              ページ1
            </Link>
          </li>
          <li className="nav-item">
            <Link to="/page-2" className="nav-link">
              ページ2
            </Link>
          </li>
          <li className="nav-item">
            <Link to="/page-3" className="nav-link">
              ページ3
            </Link>
          </li>
        </ul>
        <ul className="navbar-nav ms-auto">
          <li className="nav-item">
            <Link to="https://www.google.com/" className="nav-link">
              退出する
            </Link>
          </li>
        </ul>
      </div>
    </nav>
  );
};

export default NavBar;

src/js/pages/Home.js

ホームページです。

import React from "react";

import NavBar from "../components/NavBar";

const Home = () => {
  return (
    <div>
      <NavBar />
      <div className="container-fluid">
        <h1>ようこそ!</h1>
        <p>Reactを用いたサンプルアプリケーションです。</p>
      </div>
    </div>
  );
};

export default Home;

src/js/pages/Page1.js

一つ目のページです。

import React from "react";

import NavBar from "../components/NavBar";

const Page1 = () => {
  return (
    <div>
      <NavBar />
      <div className="container-fluid">
        <h1>ページ1</h1>
        <p>一つ目のページです。</p>
      </div>
    </div>
  );
};

export default Page1;

src/js/pages/Page2.js

二つ目のページです。
チェックボックスのチェック状態によってナビゲーションバーの表示・非表示を切り替えています。

import React, { useState } from "react";

import NavBar from "../components/NavBar";

const Page2 = () => {
  const [checked, setChecked] = useState(true);

  return (
    <div>
      {checked ? <NavBar /> : undefined}
      <div className="container-fluid">
        <h1>ページ2</h1>
        <p>二つ目のページです。</p>

        <div className="form-check">
          <input
            className="form-check-input"
            type="checkbox"
            value=""
            id="shouldShowNavBarCheckbox"
            checked={checked}
            onChange={() => setChecked((prevState) => !prevState)}
          />
          <label
            className="form-check-label"
            htmlFor="shouldShowNavBarCheckbox"
          >
            ナビゲーションバーを表示する
          </label>
        </div>
      </div>
    </div>
  );
};

export default Page2;

src/js/pages/Page3.js

三つ目のページです。
テキストを入力してOKボタンを押すと、アラートが表示されます。

import React, { useState } from "react";
import { Link } from "react-router-dom";

import NavBar from "../components/NavBar";

const Page3 = () => {
  const [text, setText] = useState("");

  return (
    <div>
      <NavBar />
      <div className="container-fluid">
        <h1>ページ3</h1>
        <p>三つ目のページです。</p>

        <div className="mb-3">
          <label className="form-label" htmlFor="myRandomTextarea">
            適当に作ったテキストエリア
          </label>
          <textarea
            className="form-control"
            id="myRandomTextarea"
            rows="3"
            placeholder="Hello, world!"
            value={text}
            onChange={(e) => setText(e.target.value)}
          />
        </div>

        <div className="row justify-content-md-end">
          <div className="col-md-auto">
            <Link to="/" className="btn btn-secondary">
              キャンセル
            </Link>
          </div>
          <div className="col-md-auto">
            <button
              className={
                text === "" ? "btn btn-primary disabled" : "btn btn-primary"
              }
              onClick={() => alert(text)}
            >
              OK
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Page3;

アプリケーションの実行

npm startで開発サーバーを起動します。
ここで実際に実行されているコマンドは、package.jsonに記載しているwebpack serve --mode developmentです。
デフォルトの設定では、localhost:8080でページにアクセスできるはずです。

React

Posted by 駄場さん