Skip to content

RustのフロントエンドにReactを使う#

ReactVue,Angularに並ぶ3大フロントエンドフレームワークの一つで、Facebookが開発・メンテナンスしているJavaScriptライブラリです。

Reactの役目はHTMLをユーザの操作に応じて適切に書き換えることです。

早速、Reactを始めていきましょう。

前提#

Rustプロジェクト(こちらの手順で作成したもの)をお持ちでなければ、最初からセットアップしておいてください。

プロジェクト構造#

このチュートリアルでは、Rustのバックエンドと、Reactのフロントエンドを別々のディレクトリで管理します。これは実際の開発でよく使われるアプローチで、フロントエンドとバックエンドを独立して開発できる利点があります。

rust_web_app/
  ├── rust-web-server/ (バックエンドのRustプロジェクト)
  └── react-frontend/ (フロントエンドのReactプロジェクト)

Reactプロジェクトの作成#

まず、Reactプロジェクトを作成します。rust_web_appディレクトリで以下のコマンドを実行してください:

npx create-react-app react-frontend
cd react-frontend

これでreact-frontendディレクトリにReactプロジェクトが作成されました。

開発サーバーの起動#

まずはReactの開発サーバーを起動して、正常に動作するか確認しましょう:

npm start

ブラウザが自動的に開き、Reactのウェルカムページが表示されれば成功です。

APIと通信するためのプロキシ設定#

Reactの開発サーバーはデフォルトでlocalhost:3000で動作します。一方、Rustのバックエンドサーバーはlocalhost:8080で動作します。クロスオリジンリソース共有(CORS)の問題を避けるため、開発時はプロキシを設定します。

react-frontend/package.jsonに以下の行を追加してください:

{
  // ...既存のコンテンツ
  "proxy": "http://localhost:8080"
}

コンポーネントの作成#

Reactでは、UIの各部分をコンポーネントとして定義します。まずは簡単なコンポーネントを作成しましょう。

react-frontend/src/App.jsを以下のように編集します:

import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
  const [message, setMessage] = useState('');

  useEffect(() => {
    // コンポーネントがマウントされたらAPIからメッセージを取得
    fetch('/hello')
      .then(response => response.json())
      .then(data => setMessage(data.text))
      .catch(error => console.error('Error fetching message:', error));
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <p>{message || 'ロード中...'}</p>
      </header>
    </div>
  );
}

export default App;

この例では、useEffectフックを使って、コンポーネントがマウントされたときにRustバックエンドの/helloエンドポイントからデータを取得しています。

バックエンドとフロントエンドの連携#

完全なアプリケーションをテストするには、両方のサーバーを起動する必要があります。

  1. 一つのターミナルでRustバックエンドを起動: bash cd rust-web-server cargo run

  2. 別のターミナルでReactフロントエンドを起動: bash cd react-frontend npm start

ブラウザでhttp://localhost:3000にアクセスすると、Reactアプリケーションが表示され、「こんにちは!」というメッセージがRustバックエンドから取得されて表示されるはずです。

本格的なユーザー管理アプリケーションを作成#

次に、前回作成したユーザーAPIを使った本格的なユーザー管理アプリケーションを作成しましょう。

ユーザーリストコンポーネントの作成#

react-frontend/src/componentsディレクトリを作成し、その中にUserList.jsファイルを作成します:

mkdir -p react-frontend/src/components

react-frontend/src/components/UserList.jsの内容:

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

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // ユーザーリストを取得
    fetchUsers();
  }, []);

  const fetchUsers = () => {
    setLoading(true);
    fetch('/api/users')
      .then(response => {
        if (!response.ok) {
          throw new Error('ユーザーデータの取得に失敗しました');
        }
        return response.json();
      })
      .then(data => {
        setUsers(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  };

  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error}</div>;

  return (
    <div className="user-list">
      <h2>ユーザー一覧</h2>
      {users.length === 0 ? (
        <p>ユーザーがいません</p>
      ) : (
        <ul>
          {users.map(user => (
            <li key={user.id}>
              {user.id}: {user.account_name} - {user.name} - {user.tel}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default UserList;

ユーザー作成コンポーネントの作成#

react-frontend/src/components/UserInput.jsを作成します:

import React, { useState } from 'react';

function UserInput({ onUserCreated }) {
  const [user, setUser] = useState({
    name: '',
    account_name: '',
    tel: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUser(prevUser => ({
      ...prevUser,
      [name]: value
    }));
  };

  const isFormValid = () => {
    return user.name && user.account_name && user.tel;
  };

  const createUser = async () => {
    if (!isFormValid()) return;

    try {
      const queryParams = new URLSearchParams({
        name: user.name,
        account_name: user.account_name,
        tel: user.tel
      }).toString();

      const response = await fetch(`/api/users/create?${queryParams}`);

      if (!response.ok) {
        throw new Error('ユーザー作成に失敗しました');
      }

      const newUser = await response.json();

      // 親コンポーネントに通知
      if (onUserCreated) {
        onUserCreated(newUser);
      }

      // フォームをリセット
      setUser({
        name: '',
        account_name: '',
        tel: ''
      });
    } catch (error) {
      console.error('ユーザー作成エラー:', error);
      alert(error.message);
    }
  };

  return (
    <div className="user-input">
      <h2>新規ユーザー作成</h2>
      <div>
        <input
          type="text"
          name="name"
          placeholder="名前"
          value={user.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <input
          type="text"
          name="account_name"
          placeholder="アカウント名"
          value={user.account_name}
          onChange={handleChange}
        />
      </div>
      <div>
        <input
          type="text"
          name="tel"
          placeholder="電話番号"
          value={user.tel}
          onChange={handleChange}
        />
      </div>
      <button 
        onClick={createUser}
        disabled={!isFormValid()}
      >
        新規作成
      </button>
    </div>
  );
}

export default UserInput;

メインアプリケーションの更新#

react-frontend/src/App.jsを更新して、作成したコンポーネントを統合します:

import React, { useState } from 'react';
import './App.css';
import UserList from './components/UserList';
import UserInput from './components/UserInput';

function App() {
  const [refreshList, setRefreshList] = useState(false);

  // ユーザーが作成されたら、リストを更新
  const handleUserCreated = () => {
    setRefreshList(prev => !prev);
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>ユーザー管理アプリケーション</h1>
      </header>
      <main className="App-content">
        <UserInput onUserCreated={handleUserCreated} />
        <UserList key={refreshList} />
      </main>
    </div>
  );
}

export default App;

スタイルの追加(オプション)#

react-frontend/src/App.cssを更新して、アプリケーションに基本的なスタイルを追加します:

.App {
  text-align: center;
}

.App-header {
  background-color: #282c34;
  padding: 20px;
  color: white;
}

.App-content {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.user-input {
  margin-bottom: 30px;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 5px;
}

.user-input input {
  margin: 10px;
  padding: 8px;
  width: 300px;
}

.user-input button {
  margin-top: 10px;
  padding: 8px 15px;
  background-color: #61dafb;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.user-input button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.user-list {
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 5px;
}

.user-list ul {
  list-style-type: none;
  padding: 0;
}

.user-list li {
  padding: 10px;
  border-bottom: 1px solid #eee;
  text-align: left;
}

アプリケーションの実行#

両方のサーバーを起動していることを確認してください:

  1. Rustバックエンド(ポート8080): bash cd rust-web-server cargo run

  2. Reactフロントエンド(ポート3000): bash cd react-frontend npm start

ブラウザでhttp://localhost:3000にアクセスすると、ユーザー管理アプリケーションが表示されます。

ユーザーを作成すると、リストに表示されるはずです。これは、ReactフロントエンドがRustバックエンドのAPIと正常に通信していることを示しています。

次はAxiosを使ってサーバーと通信する方法を学びましょう!