【3日目】React初学者が1週間でどこまで出来るか挑戦(Inertiaの理解)

はじめに

React学習1週間チャレンジ3日目です。初日はBreezeパッケージのインストールで環境構築しましたが、迷走して再度InertiaとReactをマニュアルでインストールして構築しました。Inertiaについて理解が少し深まりました。

前回の振り返り:

  • 【2日目】React初学者が1週間でどこまで出来るか挑戦
    • コンポーネントのImportとExport
    • JSXのマークアップ
    • JSXの構文ルール
    • コンポーネントにpropsを渡す
    • 条件付きレンダリング
    • リストをレンダリングするときの注意
    • コンポーネントをpureに保つ
    • イベントハンドリングの基礎
    • Stateの基礎の基礎

学習ログ

最初に、Breezeパッケージで生成されたjsxファイルを見て昨日までドキュメントで学んだ知識でどこまでコードが読めるか確認してみた。

Breezeファイル構成:

  • resources/
    • js/
      • Components/
      • Layouts/
      • Pages/
        • Auth/
        • Profile/
        • Dashboard.jsx
        • Welcome.jsx
      • app.jsx

まず、アプリ/にアクセスした時に表示されるWelcome.jsxを確認してみる。

@inertiajs/reactからLinkHeadをインポートしている。

import { Link, Head } from '@inertiajs/react';

node_modules/@inertiajs/reactを確認。配下に直接LinkHeadが見当たらないがpakage.jsonによってロードするべき主要なエントリーポイントを指定され適したファイルがロードされているみたい。

次に、Welcomeメソッドがデフォルトエクスポートされている。propsが指定されている。

export default function Welcome({ auth, laravelVersion, phpVersion }) {
  // ...省略
}

呼び出し元を確認。propsにauthが見当たらない、なぜ?

// routes/web.php
Route::get('/', function () {
    return Inertia::render('Welcome', [
        'canLogin' => Route::has('login'),
        'canRegister' => Route::has('register'),
        'laravelVersion' => Application::VERSION,
        'phpVersion' => PHP_VERSION,
    ]);
});

理由はapp/Http/Middleware/HandleInertiaRequests.phpを見ると分かった。Inertia\Middlewareを継承しており、shareメソッドによってデフォルトのpropsを定義していた。その中にauthがあった。

return以降を見てみる。フラグメント(<>...</>)やコンポーネント(<Head title="Welcome" />)が使用されている。

export default function Welcome({ auth, laravelVersion, phpVersion }) {
    return (
        <>
            <Head title="Welcome" />

            {/* 省略 */}
          
        </>
    );
}

Inertiaの理解

HandleInertiaRequestsに定義された$rootViewによってルートになるviewファイル(Root Template)が決まるようだ。デフォルトはresources/views/app.blade.php

// app/Http/Middleware/HandleInertiaRequests.php
protected $rootView = 'app';

Inertia、Reactをマニュアルインストール

最初にBreezeパッケージで一緒にInertiaを導入したがもう少しInertiaを理解するために、Breezeを使わずにマニュアルでインストールしてみた。

サーバーサイドの設定

composer require inertiajs/inertia-laravel

composer.jsonに依存関係が追加された。

"inertiajs/inertia-laravel": "^0.6.10",

次にRoot Templateの作成。app.blade.phpを作る。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    @vite('resources/js/app.js')
    @inertiaHead
  </head>
  <body>
    @inertia
  </body>
</html>

Middlewareの作成

php artisan inertia:middleware

app/Http/Middleware/HandleInertiaRequests.phpが生成される。app/Http/Kernel.phpに登録する。

'web' => [
    // ...
    \App\Http\Middleware\HandleInertiaRequests::class,
],

クライアントサイドの設定(React)

npm install @inertiajs/react

composer.jsonに依存関係が追加された。

"dependencies": {
    "@inertiajs/react": "^1.0.11"
}

Viteの設定

一度Laravelのドキュメントに戻る

npm install --save-dev @vitejs/plugin-react

composer.jsonに依存関係が追加された。

"devDependencies": {
    "@vitejs/plugin-react": "^4.1.0",

vite.config.jsの設定 : configuring-vite

vite.config.jsにエントリポイントresources/js/app.jsxを追加しreact()で有効化する。

// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.jsx']),
        react(),
    ],
    server: {
        host: true,
        hmr: {
            host: 'localhost',
        },
    },
});

app.blade.phpのheadに以下を追加する。

@viteReactRefresh
@vite('resources/js/app.jsx')

react-domを追加

react-domをインストール

npm install react-dom

composer.jsonに依存関係が追加された。

"dependencies": {
    "@inertiajs/react": "^1.0.11",
    "react-dom": "^18.2.0"

テストページresources/js/Pages/Test.jsxを作る

export default function Test() {
    return (
        <>
            <h1>TestPage</h1>
            <p>this is test</p>
        </>
    );
}

web.phpにInertiaでルート定義。

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get('/', function () {
    return Inertia::render('Test');
});

npm run devでVite開発サーバーを起動してページを確認。上記テストページが表示されれば成功。

Userデータを表示する

ここから実践。Userモデルから取得したusersデータをIndex.jsxにリストで表示させてみる。Seederでダミーデータ生成済みとする。

まずはweb.phpにルーティング設定

// routes/web.php
Route::get('users', [UserController::class, 'index'])->name('users.index');

UserControllerindexメソッドを定義して、Inertia::renderメソッドを使う。

// app/Http/Controllers/UserController.php
public function index()
{
    $users = User::all();
    return Inertia::render('User/Index', [
        'users' => $users,
    ]);
}

propsに渡したusersはUserモデルを格納したCollectionである。

// resources/js/Pages/User/Index.jsx
export default function Index({ users }) {
    const userList = users.map(user =>
        <li key={user.id}>
            {user.id} : {user.name}
        </li>
    );
    return (
        <ul>
            {userList}
        </ul>
    );
}

UserTableに改良する

パスはresources/js/Pages/からの相対パスで表記。

User/UserTable.jsxを作成してテーブルをマークアップしてエクスポート。

// User/UserTable.jsx
export default function UserTable({ users }) {
    return (
        <table>
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>E-mail</th>
                </tr>
            </thead>
            <tbody>
                {users.map(user => (
                    <tr key={user.id}>
                        <td>{user.id}</td>
                        <td>{user.name}</td>
                        <td>{user.email}</td>
                    </tr>
                ))}
            </tbody>
        </table>
    );
}

Index.jsxUserTableをインポートして読み込む。

// User/Index.jsx
import UserTable from "./UserTable";

export default function Index({ users }) {
    return (
        <div>
            <h1>User List</h1>
            <UserTable users={users} />
        </div>
    );
}

独自のレイアウトを作りたい

スタイルはSCSSを使用する(過去のリポジトリから流用)

Layouts/AppLayout.jsxを作成。

export default function App({ children }) {
    return (
        <>
        <header className="layout-header">
            <div className="header-container">
                ヘッダー
            </div>
        </header>
        <main className="layout-main">
            <div className="main-container">
                {children}
            </div>
        </main>
        </>
    );
}

User/Index.jsxでインポートして使用。

import AppLayout from '@/Layouts/AppLayout';
import UserTable from "./UserTable";

export default function Index({ users }) {
    return (
        <AppLayout>
            <h1 className="title title-h1">User List</h1>
            <UserTable users={users} />
        </AppLayout>
    );
}

InertiaのPersistent layoutsでSPA実現

参考: Pages - Inertia.js

上記のUser/Index.jsxをPersistent layoutsを実現するために書き換える。これで共通のAppLayout部分はページ遷移しても維持される。例えばヘッダー部分などに適当にinputを置いてテキストを入力した状態でページ遷移してもinputは保持されていることがわかる。

import AppLayout from '@/Layouts/AppLayout';
import UserTable from "./UserTable";

const Index = ({ users }) => {
    return (
        <>
        <h1 className="title title-h1">User List</h1>
        <UserTable users={users} />
        </>
    );
}

Index.layout = page => <AppLayout children={page} />

export default Index

連載記事

  1. 【1日目】React初学者が1週間でどこまで出来るか挑戦
  2. 【2日目】React初学者が1週間でどこまで出来るか挑戦
  3. 【3日目】React初学者が1週間でどこまで出来るか挑戦