Laravel+Vue(or React)を学習し始めた人の中で”Vue(or React)をLaravelと一緒に利用する方法が複数あるみたいだけど違いがわからない”という疑問を持っている人も多いのではないでしょうか。本文書ではLaravel環境でVue(or React)を利用するための方法を本ブログの公開済みの記事と共にに紹介し、その後Inertia.js+Vueを利用した方法について基礎から詳細に説明を行っています。Inertia.jsを利用するためにはVue.jsの知識が必要となるのでVue.jsがわからない人はInertis.jsの前にVue.jsを学習することをお勧めします。

LaravelでVue(or React)を利用する方法

Laravelと一緒にVue(or React)を利用したい場合、主に3つの方法が考えられます。

(1)Vue(or React)をLaravelとは別環境にインストールをし、LaravelをバックエンドのAPIサーバとして利用する方法

LaravelではフロントエンドにReactのフレームワークNext.jsを利用したパッケージを公開しています。Laravelとは別環境でフロントエンドの開発を行うのでフロントエンド側はNext.jsに限らずVueのフレームワークのNuxt.js、フレームワークを利用しないReact/Vue/Svelte etc.でも利用することができます。

(2)Laravel環境下にInertia.jsとVue(or React)をインストールして利用する方法

Laravel Breeze, Laravel Jetstreamパッケージを利用することで特別な追加設定を行うことなくVue(or React)を利用することができます。Inertia.jsがサポートしているのは現在Vue, React, Svelteです。Vue(or React)とLaravelとのデータの送受信はInertia.jsを介して行います。この構成ではInertia.jsの理解が必要となります。

(3)Laravel環境下にVue(or React)をインストールして利用する方法(Inertia.jsは利用しない)

Laravel環境下にVue(or React)のインストールを行い、BladeファイルからコンパイルしたVue(or React)ファイルを読み込むことで利用することができます。Laravel7まではLaravel UIパッケージ(認証用パッケージ)を利用する方法としてドキュメントに記載されていました。Laravel9ではLarave UIパッケージをインストールするかパッケージを利用することなくVue(or React)をLaravelと同じ環境にインストールして利用することができます。ルーティングファイルを適切に設定することでVue Routerを利用してSPA(Single Page Application)を作成することもできます。

現行バージョン(執筆時は9)のLaravelのドキュメントに記載されているのは(1), (2)の方法です。本文書では(2)のLaravel環境下にInertia.jsを利用する方法について説明を行っています。Laravel BreezeやLaravel Jetstreamを利用することでInertia.js + Vueを利用することができますが本文書ではパッケージを利用しない方法を中心に解説しています。

macOSにLaravel 9をインストールして動作確認を行っています。

Laravelプロジェクトの作成

Inertia.jsをインストールする前にLaravelプロジェクトを作成する必要があります。Inertia.jsではJavaScriptを利用するため事前にNode.jsとnpmがインストールされている必要があります。

本文章ではLaravelプロジェクトの作成はlaravel newコマンドを利用して行います。プロジェクト名にはlaravel_inertiaとしていますが任意の名前をつけてください。


 % laravel new laravel_inertia

プロジェクトの作成後、プロジェクトフォルダlaravel_inertiaに移動してphp artisan serveコマンドを実行して開発サーバは起動します。


 % cd laravel_inertia
 % php artisan serve
Starting Laravel development server: http://127.0.0.1:8000
[Wed Apr  6 22:23:37 2022] PHP 8.0.7 Development Server (http://127.0.0.1:8000) started

ブラウザでアクセスするとLaravelの初期画面が表示されます。

Laravel9の初期画面
Laravel9の初期画面

データベースの設定

動作確認用なのでデータベースには簡易的に利用することができるSQLiteデータベースを利用します。

touchコマンドでdatabaseフォルダにデータベースファイルdatabase.sqliteを作成します。


 % touch database/database.sqlite

ファイルを作成後.envファイルでSQLiteへの接続設定を行います。環境変数DB_CONNECTIONにはデフォルトではmysqlが設定されていますがsqliteに変更します。DB_CONNECTION以外の.envファイルにある先頭にDB_がつく環境変数をすべて削除してください。

削除後migrationsフォルダにあるテーブル作成のマイグレーションファイルを利用してテーブルを作成するためにphp artisan migrateコマンドを実行します。


 % php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (3.59ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (1.85ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (1.68ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (2.62ms)

作成したusersテーブルにダミーデータを挿入するためdatabase¥seedersフォルダにあるDatabaseSeeder.phpファイルを開いてrunメソッドにあるコメントを外します。factoryメソッドの引数の10が挿入するデータ件数を表しています。


public function run()
{
    \App\Models\User::factory(10)->create();
}

10件分のダミーデータを挿入するためphp artisan db:seedコマンドを実行します。


 % php artisan db:seed
Database seeding completed successfully.

tinkerコマンドを利用してusersテーブルにデータが挿入されているか確認しておきます。


 % php artisan tinker 
Psy Shell v0.11.2 (PHP 8.0.7 — cli) by Justin Hileman
>>> User::all();
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
=> Illuminate\Database\Eloquent\Collection {#4476
     all: [
       App\Models\User {#4478
//略

ダミーデータの挿入が確認できればLaravelの環境の構築は完了です。

Inertia.jsのインストールと設定

Inertia.jsはLaravel BreezeまたはLaravel Jetstreamをインストールすることで利用することができますが理解を深めるためにここではcomposerコマンドを利用してInertia.jsのパッケージをインストールします。

インストールを含めInertia.jsについてはLaravelのドキュメントではなくInertia.jsのドキュメントで確認する必要があります。インストールはサーバサイド用(Laravel側)のパッケージとクライアント側(VueとReactでは異なるパッケージ)で別々のパッケージをインストールします。

サーバサイドの設定

最初にサーバサイド側のパッケージをインストールします。インストールにはcomposerコマンドを利用します。


 % composer require inertiajs/inertia-laravel 

インストールが完了したらresources¥viewsフォルダにapp.blade.phpファイルを作成してInertia.jsのドキュメントのRoot templateに記載されているコードをコピー&ペーストします。Inertia.jsのデフォルトの設定でappファイルを利用するように設定されているためapp.blade.phpファイルが必要となります。app.blade.phpファイルではコンパイルされたJavaScriptファイルを読み込みとInert.jsの設定(@inertia)が行われています。app.blade.phpを利用する設定は後ほどインストールを行うmiddlewareのHandleInertiaRequestsに設定されています。

root templateの設定
root templateの設定

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
    <script src="{{ mix('/js/app.js') }}" defer></script>
    @inertiaHead
  </head>
  <body>
    @inertia
  </body>
</html>

次にinertiaのmiddlewareのインストールを行います。php artisan inertia:middlewareコマンドを実行するとHttp¥MiddlewareのフォルダにHandleInertiaRequests.phpファイルが作成されます。


 % php artisan inertia:middleware
Middleware created successfully.

インストール後にHandleInertiaRequests middlewareをapp¥Http¥Kernel.phpファイルに追加する必要があります。


protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \App\Http\Middleware\HandleInertiaRequests::class, //追加
    ],

middlewareの追加をKernel.phpファイルに行うとリクエストを行う度にHandleInertiaRequestsファイルが実行されることになります。

クライアントサイドの設定

Vueを利用する場合はVue2とVue3ではインストールするパッケージが異なります。本文書ではVue3用のパッケージをインストールします。インストールはnpmコマンドまたはyarnコマンドで行うことができます。


 % npm install @inertiajs/inertia @inertiajs/inertia-vue3 
or 
 % yarn add @inertiajs/inertia @inertiajs/inertia-vue3

Vueを利用するためにvueのインストールを行います。バージョンを指定しない場合はVue3がインストールされます。


 % npm install vue

Vueのインストール完了後、resource¥js¥app.jsファイルにVueの設定を追加します。設定はInertia.jsのドキュメントに記載されているものをコピー&ペーストして利用します。


require("./bootstrap");

import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/inertia-vue3";

createInertiaApp({
    resolve: (name) => require(`./Pages/${name}`),
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mount(el);
    },
});

インストールしたVueを利用するためにLaravel mixの設定ファイルであるwebpack.mix.jsファイルにvue()を追加します。Laravel mixはwebpackを利用しています。


mix.js('resources/js/app.js', 'public/js')
    .vue()
    .postCss('resources/css/app.css', 'public/css', [
        //
    ]);

npm run watchコマンドを実行してJavaScriptファイルをビルドします。


 % npm run watch
 //略
        Additional dependencies must be installed. This will only take a moment.
 
        Running: npm install vue-loader@^16.2.0 --save-dev --legacy-peer-deps
 
        Finished. Please run Mix again.

追加でvue-loaderをインストールするようにメッセージが表示されるのでvue-loaderのインストールを行います。


 % npm install vue-loader@^16.2.0

再度npm run watchコマンドを実行します。


 % npm run watch
 //略
ERROR in ./resources/js/app.js 7:11-43
Module not found: Error: Can't resolve './Pages' in '/Users/mac/Desktop/laravel_inertia/resources/js'

webpack compiled with 1 error

app.jsファイルで設定しているPagesフォルダが存在しないためエラーになっているのでresouces¥jsフォルダの下にPagesフォルダを作成します。npm run watchコマンドを実行している場合はPagesフォルダを作成した瞬間に更新を検知して再ビルドが行われエラーは解消します。

Pagesフォルダの中にWelcome.vueファイルを作成します。VueではOptions APIとComposition APIの2つの記述方法がありますが本文書ではComposition APIのscript setupを利用して記述していきます。


<script setup>
import { onMounted } from "vue";
onMounted(() => {
    console.log("Welcome Page mounted");
});
</script>
<template>
    <h1>Welcome Inertia.js</h1>
</template>

Composition APIについては下記の文書で公開しています。

Pagesフォルダに作成したWelcome.vueファイルの内容が表示されるようにweb.phpファイルにルーティングを設定します。Inertiaクラスのrender関数の引数に作成したWelcome.vueファイルを指定します。


<?php

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

//略

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

php artisan serveコマンドを実行してブラウザでアクセスすると”Welcome Inertia.js”の文字列が表示されます。デベロッパーツールのコンソールには”Welcome Page mounted”が表示されます。

Welcome.vueファイルの内容が表示
Welcome.vueファイルの内容が表示

Inertia.jsでの設定はBladeファイルの内容を表示する際に利用できるView::make()に似ています。View::make()のヘルパー関数のview()があるようにInertia::render()のヘルパー関数にinertia()があります。


Route::get('/', function () {
    return inertia('Welcome');
});

Chromeを利用している場合は拡張機能のVue.js devtoolsをインストールしている場合はVueのコンポーネントを確認することでどのような情報が含まれているか確認することができます。

vue.js devtoolsによる確認
vue.js devtoolsによる確認

Inertia.jsの動作確認

Inertia.jsの初期設定と動作確認が確認ができたのでここからInertia.jsを利用する上で必要となる機能を確認していきます。

コンポーネントにデータを渡す

ページコンポーネントにデータを渡す際には下記のように行うことができます。greetingを使って文字列Helloを渡しています。Bladeファイルのデータを渡す時と同じ設定方法です。


Route::get('/', function () {
    return Inertia::render('Welcome',[
        'greeting' => 'Hello'
    ]);
});

Devtoolsを確認すると設定した値はpropsの中にgreetingとその値が確認できます。

Devtoolsで渡した値を確認
Devtoolsで渡した値を確認

propsの中に含まれているgreeingの値をWelcome.vueファイルではdefinePropsを利用して取得することができます。ページコンポーネントに渡したデータはVueのpropsとして扱えることがわかります。


<script setup>
import { onMounted } from "vue";

defineProps({
    greeting: String,
});
onMounted(() => {
    console.log("Welcome Page mounted");
});
</script>
<template>
    <h1>{{ greeting }} Inertia.js</h1>
</template>

ブラウザで確認するとPropsで渡された”Hello”が表示されます。

propsから渡された値を表示
propsから渡された値を表示

ページの作成とページ遷移

複数のページを作成しページを移動するための設定方法を確認します。

Pagesフォルダに新たにAbout.vueファイルを作成します。


<template>
    <h1>About</h1>
</template>

作成したAbout.vueファイルの内容を表示するためにweb.phpファイルに/aboutへのルーティングを追加します。


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

/aboutにアクセスすると”About”の文字列が表示されます。

Aboutページの表示
Aboutページの表示

WelcomeページとAboutページの2つのページを作成することができたのでページ間を移動できるようにリンクを設定します。aタグを利用してリンクを設定します。下記はAbout.vueファイルのコードですが同じ設定をWelcome.vueでも行ってください。


<template>
  <nav>
    <ul>
      <li><a href="/">Welcome</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
  <h1>About</h1>
</template>

ページ上部にリンクが表示されます。

aタグによるページの移動
aタグによるページの移動

ページを移動することはできますがページを移動するたびにページ全体が読み込まれていることがわかります。

Inertia.jsはLinkコンポーネントを持っているのでaタグの代わりに利用することができます。


<script setup>
import { Link } from "@inertiajs/inertia-vue3";
</script>
<template>
    <nav>
        <ul>
            <li><Link href="/">Welcome</Link></li>
            <li><Link href="/about">About</Link></li>
        </ul>
    </nav>
    <h1>About</h1>
</template>

aタグからLinkコンポーネントに変更後はページを移動する度にページ全体が読み込まれるのではなく更新が必要な場所のみ更新されるようになるためスムーズにページの移動を行うことができるようになります。

コンポーネント化

Welcome.vue, About.vueの2つのファイルに各ページのリンクのnavタグを追加しましたがコードは共通なのでコンポーネント化します。

jsフォルダに新たにComponentsフォルダを作成します。NavBar.vueファイルを作成して下記のリンクを記述します。コンポーネントの作成についてはVueの通常の方法と同じです。


<script setup>
import { Link } from "@inertiajs/inertia-vue3";
</script>
<template>
    <nav>
        <ul>
            <li><Link href="/">Welcome</Link></li>
            <li><Link href="/about">About</Link></li>
        </ul>
    </nav>
</template>

Welcome.vue, About.vueでは作成したNavBarをimportして利用します。About.vueファイルだけではなくWelcome.vueファイルも更新してください。navタグの部分をコンポーネント化しても表示に変化はありません。


<script setup>
import NavBar from "../Components/NavBar";
</script>
<template>
    <NavBar />
    <h1>About</h1>
</template>

名前付きルートの設定(Ziggy)

Linkのhref属性にはURL(/, /about)を記述していましたがLaravelではヘルパー関数のrouteの引数にルーティングで設定した名前を指定することで名前からURLを設定することができます。

ヘルパー関数のrouteを利用するためにはルーティングへ名前の設定を行っておく必要があります。nameメソッドを利用して/(ルート)のルーティングにはwelcome, /aboutのルーティングにはaboutと名前をつけています。


Route::get('/', function () {
    return Inertia::render('Welcome',[
        'greeting' => 'Hello'
    ]);
})->name('welcome');

Route::get('/about', function () {
    return Inertia::render('About');
})->name('about');

NavBarコンポーネントに設定しているLinkのhref属性にv-bindディレクティブを設定しroute関数の引数にはweb.phpファイルのルーティングで設定した名前を設定します。


<script setup>
import { Link } from "@inertiajs/inertia-vue3";
</script>
<template>
    <nav>
        <ul>
            <li><Link :href="route('welcome')">Welcome</Link></li>
            <li><Link :href="route('about')">About</Link></li>
        </ul>
    </nav>
</template>

設定後にブラウザで確認するとデベロッパーツールのコンソールにメッセージ([Vue warn]: Property “route” was accessed during render but is not defined on instance.)が表示され動作しません。Laravelのヘルパー関数routeはPHPの関数なのでBladeファイル上でのみ利用することができます。

Vue(JavaScript)上でヘルパー関数のrouteを利用するためにZiggyパッケージをインストールして設定を行う必要があります。

Ziggy/TIGHTEN
Ziggy/TIGHTEN

composerコマンドでZiggyパッケージのインストールを行います。


 % composer require tightenco/ziggy

Ziggyのドキュメントにはインストール後の設定について”Add the @routes Blade directive to your main layout (before your application’s JavaScript), and the route() helper function will now be available globally!”と記述されているのでapp.blade.phpファイルに@routesディレクティブを追加します。


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet" />
    <script src="{{ mix('/js/app.js') }}" defer></script>
    @inertiaHead
    @routes
  </head>
  <body>
    @inertia
  </body>
</html>

@routesディレクティブを設定しても先ほどと同じメッセージが表示されます。メッセージを解消するためにはapp.jsファイルのmixinを利用して下記の設定を行います。Bladeファイル(app.blade.php)に@routesを設定することでJavaScriptファイルからroute関数を利用することができるようになります。


createInertiaApp({
    resolve: (name) => require(`./Pages/${name}`),
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mixin({ methods: { route } })
            .mount(el);
    },
});

ここまででZiggyの設定は完了です。設定後はroute関数を設定する前と同様にページの移動が可能となります。

名前付きルートを設定するメリットは後ほどアプリケーション設計の変更によりURLがaboutから/company/aboutに変更になったとしてもLinkのhrefの設定を変更する必要がないことです。URLでhrefを設定している場合はリンクが設定されているすべてのページで/aboutを/company/aboutに変更する必要があります。

@routesディレクティブを追加することでブラウザからソースコードを確認するとZippy変数が設定されていることが確認できます。

ソースにより@routesの確認
ソースにより@routesの確認

コントローラーからの画面表示

これまではルーティングファイルweb.phpファイルから直接Inertiaのrender関数を実行していましたがコントローラーファイルからrender関数を実行するためUserController.phpファイルを作成します。


 % php artisan make:controller UserController
Controller created successfully.

コマンドを実行するとapp¥Http¥Controllersの下にUserController.phpファイルが作成されます。作成されたUserController.phpファイルにindexメソッドを追加します。


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;

class UserController extends Controller
{
    public function index(){
        return Inertia::render('User/Index',[
            'users' => User::all()
        ]);
    }
}

render関数の引数に設定したUser/IndexはPagesフォルダの下のUserフォルダのIndex.vueファイルを示しています。UserフォルダもIndex.vueファイルも存在しないので作成します。Index.vueファイルに対してUserモデルを利用して取得したユーザ情報をusers変数で渡しています。

UserフォルダのIndex.vueファイルではusers変数をpropsで受け取ることができます。definePropsを設定を行い受け取ったusersをtemplateタグの中で展開してユーザ名を表示します。


<script setup>
defineProps({
    users: Array,
});
</script>
<template>
    <h1>ユーザ一覧</h1>
    <ul>
        <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
</template>

web.phpファイルにルーティングを追加します。


use App\Http\Controllers\UserController;
//略
Route::get('/user', [UserController::class, 'index'])->name('user');

追加した/userにブラウザからアクセスすると画面上にはユーザ名の一覧が表示されます。

ユーザ一覧の表示
ユーザ一覧の表示

このようにInertia.jsではコントローラーの中でUserモデルを介してデータベースから取得したデータをpropsでVueファイルに渡し表示することができます。

作成したユーザページへのリンクをNavBarコンポーネントに追加します。


<ul>
    <li><Link :href="route('welcome')">Welcome</Link></li>
    <li><Link :href="route('about')">About</Link></li>
    <li><Link :href="route('user')">User</Link></li>
</ul>

User¥Index.vueファイルにもNavBarをimportします。


<script setup>
import NavBar from "../../Components/NavBar";

defineProps({
    users: Array,
});

</script>
<template>
    <NavBar />
    <h1>ユーザ一覧</h1>
    <ul>
        <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
</template>

Inertiaを利用しない場合

Inertia.jsを利用しない場合のユーザ情報の取得方法について確認しておきましょう。Inertia.jsを利用しない場合は下記のようにreactiveなデータを定義してaxiosを利用して取得したデータを保存し展開する必要があります。


<script setup>
import { ref } from "vue";

const users = ref([]);
axios.get("/api/user").then((response) => (users.value = response.data));
</script>
<template>
    <h1>ユーザ一覧</h1>
    <ul>
        <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
</template>

APIのルーティングファイルapi.phpに/userのルーティングを追加します。


Route::get('/user',function(){
    return User::all();
});

どちらも表示される結果は同じですがInertia.jsを利用した場合と利用しない場合では設定する内容が異なることが理解できるかと思います。Inertia.jsを利用する場合はコントロールファイルにInertiaのrender関数を設定してpropsでデータを渡すのでBladeファイルを利用した方法のようにLaravelと密接な関係を持っていることがわかります。

Activeステータスの設定

3つのページのリンクが表示されていますがリンクを見ても現在どのページを表示しているかはわかりません。リンクを見ることで現在どのページを表示しているかわかるように設定を行っていきます。

表示しているページのURLとページコンポーネントの情報はpageオブジェクトから取得することができます。この情報を利用することで現在表示しているページのリンクにclassを適用します。

pageオブジェクトは今後他の機能でも利用する重要なオブジェクトです。

最初にNavBarコンポーネントを使ってpageオブジェクトの動作確認を行います。


<script setup>
import { Link } from "@inertiajs/inertia-vue3";
</script>
<template>
    <div>URL: {{ $page.url }}</div>
    <div>Component: {{ $page.component }}</div>
    <nav>
        <ul>
            <li><Link :href="route('welcome')">Welcome</Link></li>
            <li><Link :href="route('about')">About</Link></li>
            <li><Link :href="route('user')">User</Link></li>
        </ul>
    </nav>
</template>

ブラウザ上には表示しているURLとページコンポーネントを確認することができます。

URLとComponentを確認
URLとComponentを確認

URLとコンポーネントの情報が取得できたのでどちらかの情報を利用して表示しているページのリンクにclassを設定することができます。classの設定にはv-bindディレクティブを利用します。

Userページを表示している時にリンクをネイビーの太文字で表示できるようにactiveクラスをclass属性に追加しv-bindで条件を記述します。pageオブジェクトのurlを利用してpage.urlと/userが一致する場合はtrueが戻されるのでactiveクラスが適用されます。


<script setup>
import { Link } from "@inertiajs/inertia-vue3";
</script>
<template>
    <nav>
        <ul>
            <li><Link :href="route('welcome')">Welcome</Link></li>
            <li><Link :href="route('about')">About</Link></li>
            <li>
                <Link
                    :href="route('user')"
                    :class="{ active: $page.url === '/user' }"
                    >User</Link
                >
            </li>
        </ul>
    </nav>
</template>
<style scoped>
.active {
    font-weight: bold;
    color: navy;
    text-decoration: none;
}
</style>

ブラウザで確認するとUserページを表示している時はリンクのUserの文字列がネイビーで太文字になることが確認できます。他のページに移動するとactiveクラスは適用されないので元の文字に戻ります。

activeクラスの適用
activeクラスの適用

他のリンクにも同様の設定を行います。


<ul>
    <li>
        <Link
            :href="route('welcome')"
            :class="{ active: $page.url === '/' }"
            >Welcome</Link
        >
    </li>
    <li>
        <Link
            :href="route('about')"
            :class="{ active: $page.url === '/about' }"
            >About</Link
        >
    </li>
    <li>
        <Link
            :href="route('user')"
            :class="{ active: $page.url === '/user' }"
            >User</Link
        >
    </li>
</ul>

URLではなくコンポーネントの情報を利用したい場合には下記のように行うことができます。


<Link
    :href="route('user')"
    :class="{ active: $page.component === 'User/Index' }"
    >User</Link
>

現在のリンクの設定ではページを移動すると現在表示しているページのリンクにactiveクラスが適用されます。

しかしユーザ一覧ではなく個別のユーザの情報をアクセスするために/user/1というような場合には1という値が固定値ではなくなるため今回の設定では対応することができます。そのような場合はstartsWithメソッドを利用して先頭の文字列が一致するかの条件でactiveクラスを適用するかを判断します。


<Link
    :href="route('user',id)"
    :class="{ active: $page.url.startsWith('/users' }"
    >User</Link
>

レイアウトの設定

レイアウトの設定を行うことで複数のページで同じレイアウトを適用することができます。レイアウトの変更が必要な場合は1つのレイアウトファイルを更新するだけなのでメンテナンスが楽になります。

resouces¥jsフォルダの下にLayoutsフォルダを作成してLayout.vueファイルを作成します。Layoutファイルの中ではNavBarをimportして利用します。Layoutファイルを利用するページコンポーネントの中身を表示するためにslotを利用します。


<script setup>
import NavBar from "../Components/NavBar.vue";
</script>
<template>
    <NavBar />
    <slot />
</template>

About.vueファイルをLayoutコンポーネントを利用して書き換えます。表示する内容はLayoutタグの中に挿入します。


<script setup>
import Layout from "../Layouts/Layout.vue";
</script>
<template>
    <Layout><h1>About</h1></Layout>
</template>

Welcome.vue、User¥Index.vueファイルでも同様にLaytoutコンポーネントをimportして設定を行ってください。

Layout.vueファイルとNavBar.vueファイルにstyle属性を追加してスタイルを設定します。


<script setup>
import NavBar from "../Components/NavBar.vue";
</script>
<template>
    <header>
        <h1 style="text-align: center">Logo</h1>
        <NavBar />
    </header>
    <main>
        <slot />
    </main>
</template>

<script setup>
import { Link } from "@inertiajs/inertia-vue3";
</script>
<template>
    <nav>
        <ul
            style="
                display: flex;
                justify-content: space-around;
                list-style-type: none;
            "
        >
            <li>
                <Link
                    :href="route('welcome')"
                    :class="{ active: $page.url === '/' }"
                    >Welcome</Link
                >
            </li>
            <li>
                <Link
                    :href="route('about')"
                    :class="{ active: $page.url === '/about' }"
                    >About</Link
                >
            </li>
            <li>
                <Link
                    :href="route('user')"
                    :class="{ active: $page.component === 'User/Index' }"
                    >User</Link
                >
            </li>
        </ul>
    </nav>
</template>
<style scoped>
.active {
    font-weight: bold;
    color: navy;
    text-decoration: none;
}
</style>

ブラウザを確認するとWelocme, About, UserページのヘッダーにLogoの文字列と他のページへ移動するためのナビゲーションが表示されます。

レイアウトファイルの適用
レイアウトファイル適用後のUserページ

Persistent Layoutsの設定

Layoutファイルの設定を行うことができましたが現在の設定ではページを移動するためにLayoutコンポーネントのマウント、アンマウントが行われます。

マウント、アンマウントが行われているのか確認するためにLayout.vueファイルにライフサイクルフックのonMountedとonUnmountedを設定します。


import { onMounted, onUnmounted } from "vue";
import NavBar from "../Components/NavBar.vue";
onMounted(() => {
    console.log("Mount Layout");
});
onUnmounted(() => {
    console.log("UnMount Layout");
});

設定後ページを移動するとページを移動するたびにブラウザのデベロッパーツールのコンソールに”UnMount Layout”と”Mount Layout”が表示されます。

後ほどPersistent Layoutsとの比較を見るためにdevtoolsを利用してコンポーネントの情報を確認しておきます。

Aboutコンポーネントの内側にLayoutコンポーネントが含まれていることが確認できます。

devtoolsによるLayoutコンポーネントの情報
devtoolsによるLayoutコンポーネントの情報

About.vueファイルでPersistent Layoutsの設定を行います。setup scriptでの設定方法がわからなかったのでscriptタグを利用して設定を行っています。下記のようにPersistent Layoutsを利用した場合はtemplateタグ内で利用していたLayoutタグを削除することができます。


<script>
import Layout from "../Layouts/Layout.vue";

export default {
    layout: Layout,
};
</script>

<template>
    <h1>About</h1>
</template>

設定後表示される内容は変わりませんがdevtoolsを利用すると先ほどまではAboutタグの中に入っていたLayoutタグがAboutタグの外側に表示されていることが確認できます。

devtoolsによるLayoutコンポーネントの確認
devtoolsによるLayoutコンポーネントの確認

Welcome.vue、User/Index.vueファイルでも同様にPersistent Layoutsの設定を行います。Composition APIではscript setup, scriptタグを同時に利用することができるので下記のように記述することができます。


<script setup>
defineProps({
    greeting: String,
});
</script>
<script>
import Layout from "../Layouts/Layout.vue";
export default {
    layout: Layout,
};
</script>
<template>
    <h1>{{ greeting }} Inertia.js</h1>
</template>

すべてのファイルにPersistent Layoutsを設定後、ページの移動を行ってください。最初のアクセス時には”Mount Layout”が表示されますがページを移動しても先ほどのように”UnMount Layout”、”Mount Layout”のメッセージがコンソールに表示されることはありません。Layoutコンポーネントがマウント、アンマウントされることなく保持されていることがわかります。

デフォルトレイアウトの設定

Persistent Layoutsを設定した場合はapp.jsで設定したresolveコールバックを利用してデフォルトのレイアウトを設定することができます。デフォルトのレイアウトを設定することでページ毎にLayoutを設定する必要がなくなります。

app.jsの設定についてはInertia.jsのドキュメントに記載があるのでresolveコールバックをコピー&ペーストします。Layoutコンポーネントも忘れずにimportします。


require("./bootstrap");

import { createApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/inertia-vue3";
import Layout from "./Layouts/Layout.vue";

createInertiaApp({
    resolve: (name) => {
        const page = require(`./Pages/${name}`).default;
        page.layout = page.layout || Layout;
        return page;
    },
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mixin({ methods: { route } })
            .mount(el);
    },
});

ページ毎のLayoutの設定を削除します。About.vueファイルでは表示させたい内容のみ記述するだけでレイアウトが適用されるようになります。


<template>
    <h1>About</h1>
</template>

Welcome.vueファイルでのLayoutの設定を削除すると下記のように記述することができます。


<script setup>
defineProps({
    greeting: String,
});
</script>
<template>
    <h1>{{ greeting }} Inertia.js</h1>
</template>

User/Index.vueでも同様にLayoutの設定を削除してください。

デフォルトではないレイアウトを設定する方法についても確認しておきます。Layoutsフォルダに新たにUserLayout.vueファイルを作成します。Layout.vueファイルと内容はほとんど同じですが”Logo”ではなく”User”に変更しtext-alignの中央表示を削除しています。


<script setup>
import NavBar from "../Components/NavBar.vue";
</script>
<template>
    <header>
        <h1>User</h1>
        <NavBar />
    </header>
    <main>
        <slot />
    </main>
</template>

User¥Index.vueファイルでデフォルト以外レイアウトを利用したい場合は下記のように行うことができます。


<script setup>
defineProps({
    users: Array,
});
</script>
<script>
import UserLayout from "../../Layouts/UserLayout";

export default {
    layout: UserLayout,
};
</script>

<template>
    <h1>ユーザ一覧</h1>
    <ul>
        <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
</template>

ブラウザで確認するとデフォルトとは異なるレイアウトが適用されていることが確認できます。

デフォルト以外のレイアウトを適用
デフォルト以外のレイアウトを適用

Title設定

デフォルトではtitleタグに何も設定を行っていないためブラウザタブを確認するとURLが表示されています。

ブラウザのタグに表示されている内容の確認
ブラウザのタグに表示されている内容の確認

titleを設定したい場合にはHeadコンポーネントを利用することができます。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
</script>
<template>
    <Head>
        <title>Aboutページ</title>
    </Head>
    <h1>About</h1>
</template>

Headコンポーネントタグの間にtitleタグを挿入することでtitleタグの間に入れた内容がheadタグのtitleタグに設定されます。デベロッパーツールで要素の情報を確認するとtitleタグの中にはinertiaという文字列を確認することもできます。

titleの確認
titleの確認

短縮系として下記のように記述することもできます。


<Head title="Aboutページ" />

About.vueファイルでHeadタグを利用することでtitleを設定することができました。About.vueファイルのように個別にtitleを設定することができますが複数のページに一括で設定したい場合にはLayoutファイルを利用することができます。Layout.vueファイルでもAbout.vueで設定した方法と同じ方法でtitleの設定を行います。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
import NavBar from "../Components/NavBar.vue";
</script>
<template>
    <Head> <title>My App</title></Head>
    <header>
        <h1 style="text-align: center">Logo</h1>
        <NavBar />
    </header>
    <main>
        <slot />
    </main>
</template>

Layout.vueファイルを利用しているWelcome.vueファイルではHeadコンポーネントを利用していないためtitleには”My App”が設定されます。About.vueファイルではtitleが設定されているためLayout.vueファイルのtitleが上書きされてAbout.vueファイルで設定した”Aboutページ”が設定されます。

本ブログでもタイトルには”ページのタイトル | アールエフェクト”というように文字数が許す限りタイトルの最後に” | アールエフェクト”を追加しています。同じ文字列を繰り返しページに設定するのを避けるためapp.jsのcreateInertiaAppのtitleを利用することができます。


createInertiaApp({
    resolve: (name) => {
        const page = require(`./Pages/${name}`).default;
        page.layout = page.layout || Layout;
        return page;
    },
    setup({ el, App, props, plugin }) {
        createApp({ render: () => h(App, props) })
            .use(plugin)
            .mixin({ methods: { route } })
            .mount(el);
    },
    title: (title) => `${title} | アールエフェクト`,
});

設定後Aboutページを確認するとタイトルにapp.jsファイルで設定したtitleで設定した関数の引数のtitleにはHeadコンポーネントで設定したtitleの値が設定されることがわかります。

titleの確認
titleの確認

WelcomeページにはHeadコンポーネントを設定していないのでLayoutコンポーネントで設定した値が設定されます。

Layout.vueファイルのtitleが設定
Layout.vueファイルのtitleが設定

Layout.vueファイルのHeadコンポーネントを削除した場合にはWelcomeページでは” | アールエフェクト”が表示されると思うかもしれせんがWelcomeページにHeadコンポーネントが設定されていない場合にはデフォルトのURL(127.0.0.1:8000)が表示されます。titleを設定するためにHeadコンポーネントを設定しておく必要があります。

metaの設定

metaタグを設定する場合にもHeadコンポーネントを利用することができます。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
</script>

<template>
    <Head>
        <title>Aboutページ</title>
        <meta name="description" content="これはAboutページです" />
        <meta name="keywords" content="About" />
    </Head>
    <h1>About</h1>
</template>

metaタグのdescriptionとkeywordsをAbout.vueファイルに設定後ブラウザで確認すると設定したmetaタグのdescriptionとkeywordsを確認することができます。。

metaタグの確認
metaタグの確認

Layout.vueファイルでdescription, keywordsを設定した場合にtitleと同様に上書きされるのか確認します。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
import NavBar from "../Components/NavBar.vue";
</script>
<template>
    <Head>
        <title>My App</title>
        <meta name="description" content="My Appアプリケーション" />
        <meta name="keywords" content="My App" />
    </Head>
    <header>
        <h1 style="text-align: center">Logo</h1>
        <NavBar />
    </header>
    <main>
        <slot />
    </main>
</template>

ブラウザで確認するとtitleは1つしか設定されていませんがdescriptionとkeywordsについては2つ登録されていることが確認できます。

metaタグは上書きされない
metaタグは上書きされない

metaタグが複数個登録されるのを防ぐためにhead-key属性があります。Layout.vueファイルとAbout.vueファイルのどちらのmetaタグにもhead-key属性を追加します。値にはmetaタグのnameの値を設定します。


<Head>
    <title>My App</title>
    <meta
        head-key="description"
        name="description"
        content="My Appアプリケーション"
    />
    <meta head-key="keywords" name="keywords" content="My App" />
</Head>

About.vueファイルにも設定を行います。


<Head>
    <title>Aboutページ</title>
    <meta
        head-key="description"
        name="description"
        content="これはAboutページです"
    />
    <meta head-key="keywords" name="keywords" content="About" />
</Head>

設定後はAbout.vueのmetaタグの設定のみ表示されていることが確認できます。またinertia属性の値にはhead-keyで設定した値が確認できます。

head-key属性を設定後のmetaタグ
head-key属性を設定後のmetaタグ

Layout.vueファイルとAbout.vueファイルのhead-keyが異なる場合はどちらのタグも表示されます。head-keyが同じであればhead-keyの値には任意の名前を設定することができます。mateタグのdescriptionを設定している場合にはhead-keyの値を必ずdescriptionに設定する必要はありません。

Formsの設定

Inertia.jsにおけるフォームの作成方法について確認していきます。

前準備

usersテーブルにユーザを追加するための入力フォームを作成するため入力フォーム用のページを作成します。resources¥js¥Pages¥Userの下にCreate.vueファイルを作成します。/user/createでアクセスした場合にCreate.vueファイルの内容が表示されるようにルーティングの変更を行います。

現在現在のルーティング設定はgetリクエストのみ受け付ける設定になっています。


Route::get('/user', [UserController::class, 'index'])->name('user');

上記のルーティングを変更します。


Route::resource('/user', UserController::class);

名前付きルーティングを利用していますが上記の設定では自動でルーティングに名前がつけられます。名前はphp artisan route:listで確認することができます。user.index, user.storeといったものが名前です。

ルーティングテーブルの確認
ルーティングテーブルの確認

名前付きルートを利用してリンクを設定しているのでNavBar.vueファイルを更新する必要があります。route関数の引数をuserからuser.indexに更新しています。


<Link
    :href="route('user.index')"
    :class="{ active: $page.component === 'User/Index' }"
    >User</Link
>

先ほど作成したCreate.vueファイルに以下を記述します。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
</script>
<template>
    <Head title="ユーザの登録"></Head>
    <h1>ユーザの登録</h1>
</template>

作成したCreate.vueファイルが表示されるようにUserController.phpファイルにcreateメソッドを追加します。render関数の引数にCreate.vueファイルを指定します。


public function create(){
    return Inertia::render('User/Create');
}

ブラウザから/user/createにアクセスするとユーザ登録ページが表示されます。

ユーザの登録ページ
ユーザの登録ページ

ユーザの一覧ページ(User¥Index.vue)からアクセスできるようにリンクを追加します。リンクの設定にはLinkコンポーネントを利用しますがデフォルトではaタグとして設定されますがas属性を利用するとことで他の要素として設定することができます。下記ではボタン要素として設定しています。


<h1>ユーザ一覧</h1>
<div>
    <Link :href="route('user.create')" as="button" type="button" >ユーザ登録</Link>
</div>

Linkコンポーネントがボタン要素として表示されていることが確認できます。ボタンをクリックするとユーザ登録画面に移動します。

ユーザ登録のボタンを追加
ユーザ登録のボタンを追加

入力フォームの追加

入力したデータを保存するためreactive関数を利用します。またPOSTリクエストにはimportしたInertiaを利用しています。


<script setup>
import { reactive } from "vue";
import { Inertia } from "@inertiajs/inertia";
import { Head } from "@inertiajs/inertia-vue3";

const form = reactive({
    name: null,
    email: null,
    password: null,
});

function submit() {
    Inertia.post("/user", form);
}
</script>
<template>
    <Head title="ユーザの登録"></Head>
    <h1>ユーザの登録</h1>
    <div>
        <form @submit.prevent="submit">
            <div>
                <label for="name">名前:</label>
                <input id="name" v-model="form.name" />
            </div>
            <div>
                <label for="email">メールアドレス:</label>
                <input id="email" v-model="form.email" />
            </div>
            <div>
                <label for="password">パスワード:</label>
                <input id="password" v-model="form.password" type="password" />
            </div>
            <button type="submit">Submit</button>
        </form>
    </div>
</template>

ブラウザで確認すると名前、メールアドレス、パスワードの入力項目を持つフォームが表示されます。

ユーザ登録用の入力フォームの表示
ユーザ登録用の入力フォームの表示

登録ボタンをクリックすると/userにPOSTリクエストが送信されますが送信先が存在しないためエラーメッセージが表示されます。

エラーメッセージの表示
エラーメッセージの表示

UserControllerにstoreメソッドが存在しないためにエラーメッセージが表示されているのでstoreメソッドを追加します。


public function store()
{
    //ユーザ登録処理
    return redirect()->route('user.index');
}

redirect関数を利用することでユーザ登録処理の後にユーザの一覧ページにリダイレクトされます。通常のBladeファイルを利用したLaravelのリダイレクトのようにリダイレクトされる際にページ全体が再読み込みされることはなく更新が必要な箇所のみ更新されます。

axiosなどを利用してPOSTリクエストを送信した場合は処理が完了してJSONデータでユーザ情報が戻された後にクライアント側でリダイレクトの処理を行いますがInertiaの場合はサーバ側でリダイレクトの処理を行うことができます。

ユーザ登録の処理を追加します。


public function store()
{
      User::create(request()->all());

    return redirect()->route('user.index');
}

storeメソッドにユーザ登録の処理を追加したので入力フォームにユーザ情報を入力して”Submit”ボタンをクリックします。

ユーザの登録
ユーザの登録

ユーザの登録が完了するとユーザ一覧の画面にリダイレクトされ登録されたユーザが表示されていることを確認することができます。

登録したユーザの確認
登録したユーザの確認

バリデーションの設定

ユーザ登録時にバリデーションの設定を行っていたなかったのでバリデーションの設定を追加します。


public function store(Request $request)
{

    $validated = $request->validate([
            'name' => ['required', 'max:50'],
            'password' => ['required', 'max:50'],
            'email' => ['required', 'max:50', 'email'],
        ]
    );

    User::create($validated);

    return redirect()->route('user.index');
}

バリデーションを設定後ユーザ登録画面のフォームに何も入力せずに”登録”ボタンをクリックしてください。画面上には何も変化がないはずです。

デベロッパーツールのネットワークタブを開いて再度”登録”ボタンをクリックしてください。ヘッダーを確認するとステータスコード200を確認できます。

ステータスコードの確認
ステータスコードの確認

プレビューを確認するとpropsのerrorオブジェクトの中にバリデーションエラーのメッセージが保存されていることが確認できます。

ネットワークタブのプレビューを確認
ネットワークタブのプレビューを確認

さらにvue devtoolsを確認します。pageオブジェクトのpropsのerrorsオブジェクトにメッセージが保存されていることがわかります。

Vue Devtooslでオブジェクトを確認
Vue Devtooslでオブジェクトを確認

エラーメッセージが保存されていることが確認できたのでページコンポーネント上で表示する方法を確認します。

エラーメッセージはpropsオブジェクトに含まれていることがわかったのでpropsから取得して表示します。

definePropsでpropsのerrorsの設定を行いv-ifディレクティブを利用してエラーが存在する場合のみ表示されるように設定します。


<script> setup>
import { reactive } from "vue";
import { Inertia } from "@inertiajs/inertia";
import { Head } from "@inertiajs/inertia-vue3";

defineProps({
    errors: Object,
});

const form = reactive({
    name: null,
    email: null,
    password: null,
});

function submit() {
    Inertia.post("/user", form);
}
</script>
<template>
    <Head> title="ユーザの登録"></Head>
    <h1>ユーザの登録</h1>
    <div>
        <form @submit.prevent="submit">
            <div>
                <label> for="name">名前:</label>
                <input id="name" v-model="form.name" />
                <div> v-if="errors.name">{{ errors.name }}</div>
            </div>
            <div>
                <label> for="email">メールアドレス:</label>
                <input id="email" v-model="form.email" />
                <div> v-if="errors.email">{{ errors.email }}</div>
            </div>
            <div>
                <label> for="password">パスワード:</label>
                <input id="password" v-model="form.password" type="password" />
                <div> v-if="errors.password">{{ errors.password }}</div>
            </div>
            <button> type="submit">登録</button>
        </form>
    </div>
</template>

設定後に入力フォームに何も入力せず”登録”ボタンをクリックするとバリデーションエラーが表示されます。

バリデーションエラーの表示
バリデーションエラーの表示

definePropsを利用してerrorオブジェクトに保存されているメッセージにアクセスを行いましたがdefinePropsを利用せず$pageオブジェクトからアクセスすることも可能です。


<div v-if="$page.props.errors.name">
    {{ $page.props.errors.name }}
</div>

バリデーションが発生するとリダイレクトが自動で行われて入力フォームの画面が再度表示されますがリダイレクトされる前に入力した値は保存されているので入力した値をinput要素に再入力するといった処理は必要ありません。

Error bagsの設定

Inertiaを利用してPOSTリクエストを送信する際にError Bagsの設定を行うことができます。


function submit() {
    Inertia.post("/user", form, { errorBag: "createUser" });
}

errorBagで”createUser”を設定すると通常はpage.props.errorsオブジェクトに保存されるバリデーションのエラーメッセージがpage.props.errors.createUserオブジェクトに保存されます。

errorBagが活躍する場面は1つのコンポーネント上に複数のフォームが存在する場合です。どちらのフォームにもnameという入力項目がある場合はnameのバリデーションのエラーメッセージがpage.props.errors.nameに保存されるためどちらのフォームでのエラーが区別つきません。errorBagを利用することでnameのエラーはerrorBagで指定したオブジェクトに保存されるのでどちらのフォームのPOSTリクエストでのエラーか区別することが可能になります。

FormヘルパーのuseFormの利用

先ほど確認した通りInertiaを利用することでPOSTリクエストの送信もバリデーションエラーも処理することができました。Inertia.jsではさらにフォーム処理が効率よく行えるようにForm helperのuseFormを利用することができます。内部ではInertiaを利用しています。

useFormを利用して先ほどのCreate.vueのコードを書き換えます。errorsオブジェクトはdefinePropsを利用して取得していましたがuseFormから戻されるオブジェクトのformに含まれるので削除しています。reactvieの設定もuseFormの中で処理されるため削除することができます。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
import { useForm } from "@inertiajs/inertia-vue3";

function submit() {
    form.post("/user");
}

const form = useForm({
    email: null,
    password: null,
    remember: false,
});
</script>
<template>
    <Head title="ユーザの登録"></Head>
    <h1>ユーザの登録</h1>
    <div>
        <form @submit.prevent="submit">
            <div>
                <label for="name">名前:</label>
                <input id="name" v-model="form.name" />
                <div v-if="form.errors.name">{{ form.errors.name }}</div>
            </div>
            <div>
                <label for="email">メールアドレス:</label>
                <input id="email" v-model="form.email" />
                <div v-if="form.errors.email">{{ form.errors.email }}</div>
            </div>
            <div>
                <label for="password">パスワード:</label>
                <input id="password" v-model="form.password" type="password" />
                <div v-if="form.errors.password">
                    {{ form.errors.password }}
                </div>
            </div>
            <button type="submit">登録</button>
        </form>
    </div>
</template>

userFormはForm helperということで他にもさまざまな関数とプロパティを持っています。form.resetメソッドを利用することでユーザ登録に失敗した場合には入力値をすべてクリアにすることもできます。onErrorイベントはPOSTリクエストに失敗した場合に実行される関数です。onErrorの他にはonStart, onFinishなどのイベントがあります。


function submit() {
    form.post("/user", {
        onError: () => form.reset(),
    });
}

バリデーションに失敗してリダイレクトされると入力した値がすべてリセットされます。

POSTリクエストの処理が実行中かどうかを識別するためのprocessingプロパティもあります。processingプロパティを利用することでPOSTリクエスト処理中はボタンをdisabledにすることができます。disabledにすることでボタンを一度押した後に謝って複数回ボタンをクリックすることを防ぐことができます。


<button type="submit" :disabled="form.processing">登録</button>

他にForm HelperのuseFromで戻されるオブジェクトがどのような関数やプロパティを持っているかはInertiaのドキュメントもしくはVue Devtoolsを見ることで確認することができます。node_modulesの@inertiajs下に保存されているソースコードのuseForm.jsを見ることでも確認することができます。

formオブジェクトのプロパティと関数
formオブジェクトのプロパティと関数

ファイルのアップロード方法

Form HelperのuseFormを利用したファイルのアップロード方法について確認を行います。usersテーブルにアップロードしたファイルのパスを保存する列の追加を行います。php artisan migrationコマンドを利用してavatar_file_path列を追加するためのマイグレーションファイルを作成します。


 % php artisan make:migration add_avatar_to_users_table --table=users

コマンド実行後に作成されるマイグレーションファイルにavatar_file_pathを追加します。


Schema::table('users', function (Blueprint $table) {
    $table->string('avatar_file_path')->nullable();
});

マイグレーションファイルの更新が完了したらphp artisan migrateコマンドを実行し列の追加を行います。


 % php artisan migrate
Migrating: 2022_04_12_063820_add_avatar_to_users_table
Migrated:  2022_04_12_063820_add_avatar_to_users_table (5.53ms)

User.phpファイルで追加したavatar_file_path列にデータが保存できるように$fillable変数の配列に追加します。


protected $fillable = [
    'name',
    'email',
    'password',
    'avatar_file_path'
];

列の追加が完了したらCreate.vueファイルにファイルのアップロード用のinput要素を追加します。ファイルを扱うのでtype属性をfile、属性nameにavatarを設定します。


<div>
    <label for="avatar">画像:</label>
    <input
        id="avatar"
        @input="form.avatar = $event.target.files[0]"
        type="file"
    />
    <div v-if="form.errors.avatar">
        {{ form.errors.avatar }}
    </div>
</div>

useFromの引数のオブジェクトにavatarを追加して初期値をnullとします。


const form = useForm({
    name: null,
    email: null,
    password: null,
    avatar: null,
});

ユーザの登録画面を表示するとファイル選択が表示されます。

ファイル選択が追加されたフォーム
ファイル選択が追加されたフォーム

ファイルを選択して”登録”ボタンをクリックする前に選択したファイルがPOSTリクエストに含まれているか確認するためにUserController.phpファイルのstoreメソッドでdd関数を利用してリクエストの中身を確認します。


public function store(Request $request)
{

    dd($request->all());
//略

storeメソッドで設定が完了したら入力フォームでファイルを選択して”登録”ボタンをクリックしてください。POSTリクエストで送信されてきたファイルをコントローラーで受け取れることが確認できます。

送信されてきた画像の確認
送信されてきた画像の確認

コントローラーで送信したファイルを受け取ることができたら画像の保存方法については通常のLaravelでの設定方法と同じです。

保存したファイルがブラウザから閲覧できるようにシンボクックリンクの設定を行います。


% php artisan storage:link

実行すると/storage/app/public以下に保存したファイルがブラウザ上からアクセス可能になります。

storeメソッドにファイルを保存するための処理を追加します。ファイルを選択してリクエストを送信した場合は/storage/app/publicの下にファイルが保存されブラウザから/storage/ファイル名でアクセスすることができるようになります。


public function store(Request $request)
{

    $validated = $request->validate([
            'name' => ['required', 'max:50'],
            'password' => ['required', 'max:50'],
            'email' => ['required', 'max:50', 'email'],
        ]
    );

    if($request->avatar){
        $file_name = $request->file('avatar')->getClientOriginalName();
        $request->file('avatar')->storeAs('public',$file_name);
        $validated['avatar_file_path'] = '/storage/'.$file_name;
    }

    User::create($validated);

    return redirect()->route('user.index');
}

入力フォームに入力後ファイルを選択して登録ボタンをクリックしてファイルのアップロードを行ってください。リダイレクトされるUser¥Index.vueファイルでアップロードした画像を確認できるように更新を行います。avatar_file_pathに値がある時のみ画像を表示させます。


<ul>
    <li
        v-for="user in users"
        :key="user.id"
        style="display: flex; align-items: center"
    >
        <span v-if="user.avatar_file_path"
            ><img :src="user.avatar_file_path" style="width: 30px"
        /></span>
        <span>{{ user.name }}</span>
    </li>
</ul>

アップロードした画像がユーザ名の左側に表示されれファイルのアップロードは正常に完了しています。

アップロードした画像の表示
アップロードした画像の表示

Shared dataの設定

アプリケーション全体で共有したいデータがある場合にShared dataを利用することができます。ヘッダー上にログインが完了したユーザ名を表示したい場合や何かの処理後にユーザにメッセージを通知するフラッシュメッセージなどに利用することができます。

現在の設定ではログインの機能を持っていないのでログインユーザの利用方法についての説明は行いませんが別の方法でデータを共有する方法を確認します。データの共有はInertia.jsの初期設定時にインストールしたmiddlewareのHandleInertiaRequests.phpファイルのshareメソッドで行うことができます。

middlewareなのでページの移動などリクエストを行う度にshareメソッドが実行されます。

app.phpファイルに設定されているApplication NameをShared dataとして設定する場合に下記のように行うことができます。


public function share(Request $request): array
{
    return array_merge(parent::share($request), [
        'appName' => config('app.name'),
    ]);
}

設定後Vue Devtoolsを確認するとpropsにappNameが保存されていることが確認できます。Laravelという値になっていますが.envファイルの環境変数APP_NAMEに設定された値です。

propsにappNameを確認
propsにappNameを確認

About.vueページからアクセスする場合$pageプロパティを利用することができます。


<h1>About | {{ $page.props.appName }}</h1>

Aboutの横にLaravelが表示されます。

NavBarコンポーネントからもアクセスすることができます。ナビゲーションメニューのli要素に追加します。


<li>{{ $page.props.appName }}</li>

$pageの他にusePage関数を利用することができます。usePage関数はLayout.vueファイルで利用します。computedプロパティを利用することもできます。


<script setup>
import { Head } from "@inertiajs/inertia-vue3";
import NavBar from "../Components/NavBar.vue";
import { computed } from "vue";
import { usePage } from "@inertiajs/inertia-vue3";
const appName = computed(() => usePage().props.value.appName);
</script>

computedプロパティを利用して表示します。


<h1 style="text-align: center">{{ appName }}</h1>

computedプロパティを利用せず下記のように記述することもできます。


<h1 style="text-align: center">{{ usePage().props.value.appName }}</h1>

About.vue, NavBar.vue, Layout.vueにShared dataを設定したのでブラウザには3つのLaravelが表示されます。

ブラウザ上に表示したShard data
ブラウザ上に表示したShard data

Shared dataはフラッシュデータにも利用することができます。フラッシュデータは次のリクエストの画面にメッセージを表示させるためにSessionに保存するデータのことです。Laravelでは何か処理を行なった後にリダイレクト先でユーザに通知させるメッセージとして利用されます。

ここではユーザ登録が完了した後のリダイレクト先のユーザ一覧ページに表示させるメッセージをShared dataを利用して行います。

フラッシュデータの保存はflashメソッドを使って行うことができます。UserController.phpファイルのユーザを作成後に実行しています。


User::create($validated);

$request->session()->flash('message','ユーザの作成が完了しました');

return redirect()->route('user.index');

保存したメッセージはgetメソッドを利用して取得することができます。


$request->session()->get('message')

Shared dataにフラッシュデータを保存するためHandleInertiaRequests.phpファイルのshareメソッドに処理を追加します。


public function share(Request $request): array
{
    return array_merge(parent::share($request), [
        'appName' => config('app.name'),
        'flash' => [
            'message' => fn () => $request->session()->get('message')
        ],
    ]);
}

Shared dataに保存されたデータの取得方法はappNameの場合と同じです。$pageオブジェクトから取得することができます。User¥Index.vueファイルではflashオブジェクトのmessageにメッセーが保存されている場合のみ表示できるようにv-ifディレクティブを利用します。


<template>
    <h1>ユーザ一覧</h1>
    <div v-if="$page.props.flash.message" style="color: red; font-weight: bold">
        {{ $page.props.flash.message }}
    </div>
    <div>
        <Link :href="route('user.create')" as="button" type="button"
            >ユーザ登録</Link
        >
    </div>

ユーザ登録が完了した場合のみメッセージが表示されます。

Shared dataのflushのメッセージを表示
Shared dataのflushのメッセージを表示

Inertia.jsに記載されているすべての機能の動作確認を行なったわけではありませんがInertia.jsを使いこなう上で必要な機能を確認することができました。

LaravelでVueを利用したいという人がいたらぜひLaravel + Inertia.jsでの環境構築にチャレンジしてください。