この記事では、Inertia.jsのLinkコンポーネントについて解説します。
この連載記事ではLaravelにおけるInertia.jsの使い方を解説しています。他の記事と合わせてお読みください。
InertiaのLinkコンポーネントとは一体何でしょうか?aタグのリンクではどうしてダメなんですか?
aタグだと通常のリクエストとなり、Inertia.jsの特徴であるSPAにならないため、リンクをXHRリクエストに変換してくれるInertia Linkを使用します。
Inertia Linkとは
通常のaタグによるリンクでは、ページ移動の度にすべてのデータを読み直すHTTPリクエストが発生します。Inertiaアプリの本髄は、初回にフルHTMLなデータを読み込んだ後は、次からのページアクセス時には必要なデータだけを読み込むSPAです。
InertiaのLinkコンポーネントは、aタグをクリックした際に、通常のHTTPリクエストを行う代わりに必要なデータだけをリクエストするXHRリクエストを行うための仕組みです。
Inertia.jsを使用する際には必ず使うことになるので使い方をしっかり覚えておきましょう。
Inertia Linkの作成
通常のaタグ
<a href="/">Home</a>
このような通常のaタグによるリンクをInertia linkに変更するには、Linkコンポーネントをインポートし、aタグをLinkタグに書き換えるだけです。
<Link href="/">Home</Link>
これで、クリック時にXHRリクエストに変更するイベントハンドラの設定されたaタグが出力されます。装飾などは通常のaタグとして行えます。
buttonリンク
aタグではなく、buttonタグでリンクを作成したい場合はas属性でbuttonを指定します。
<Link href="/logout" method="post" as="button" type="button">Logout</Link>
// これは次のようなタグが出力されます:
<button type="button">Logout</button>
Inertia Linkの属性
HTTPメソッド
Inertia LinkはデフォルトではGETリクエストを行いますが、method属性で他のメソッドを使用するよう指定することもできます。対応メソッドはGETの他、POST, PUT, PATCH, DELETEです。
//POSTメソッドでリクエストを行う場合
<Link href="/logout" method="post">Logout</Link>
Data属性
data属性で送信するリクエストに付加するデータを指定できます。data属性にはオブジェクトか、FormDataインスタンスを指定できます。
<Link href="/endpoint" method="post" :data="{ foo: bar }">Save</Link>
カスタムヘッダー
headers属性で、リクエストに追加するHTTPヘッダーを指定できます。Inertiaがデフォルトで付加するヘッダーは上書きできません。
<Link href="/endpoint" :headers="{ foo: bar }">Save</Link>
履歴管理
デフォルトでは、Inertiaリンクによるページ移動では、ブラウザの履歴に移動したページが追加されていき、戻ったり進んだりできるようになっています。
replace属性をつけることで、ページ移動の際に履歴を追加するのではなく、現在の履歴を書き換えるようにすることができます。
<Link href="/" replace>Home</Link>
状態の保存
preserve-state属性をつけると、ページ移動時にフォームに入力した状態を維持させることができます。
<input v-model="query" type="text" />
<Link href="/search" :data="{ query }" preserve-state>Search</Link>
レイアウト機能でも、フォームの状態を保存することが出来ましたが、どう違うんですか?
レイアウト機能では、読み直す部分ではないページの状態が維持されましたが、preserve-stateでは読み込むページ自身にあるフォームの状態が維持されます。
スクロールの抑制
preserve-scroll属性をつけると、ページ移動時にスクロールポジションがリセットされなくなり、移動前と同じスクロール位置が維持されるようになります。
<Link href="/" preserve-scroll>Home</Link>
部分的なデータ読み込み
そのページに含まれるデータのうち、ある特定のデータだけを読み込めばいい場合、only属性でその読み込むデータを指定できます。
<Link href="/users?active=true" :only="['users']">Show active</Link>
サンプルをモデル使用版へ変更
このサンプルは前回から引き継いで使用しています。
前回のソースコードはこちらから→GitHub
モデル作成
前回まで作成していたブックマーク一覧を改良します。モデルを作成して実際にブックマークを追加できるようにします。
php artisan make:model Bookmark -ms
//実行結果
INFO Model [app/Models/Bookmark.php] created successfully.
INFO Migration [database/migrations/2023_08_15_105210_create_bookmarks_table.php] created successfully.
INFO Seeder [database/seeders/BookmarkSeeder.php] created successfully.
モデルのマイグレーションファイルを変更してtitleとurlをカラムとして追加します。
//略・・・
public function up()
{
Schema::create('bookmarks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('url');
$table->timestamps();
});
}
//略・・・
保存したらマイグレーションを実行します。
php artisan migrate
//実行結果
INFO Running migrations.
2023_08_15_105210_create_bookmarks_table ............................. 18ms DONE
シーダーの追加
あらかじめデータを挿入しておくためのシーダーを追加します。
<?php
namespace Database\Seeders;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Seeder;
class BookmarkSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('bookmarks')->insert([
[
'title' => 'SOHO MIND',
'url' => 'https://blog.shipweb.jp/',
],
[
'title' => 'どこかにアンテナ',
'url' => 'https://ant.shipweb.jp/',
],
]);
}
}
シーディングを実行します。
php artisan db:seed --class=BookmarkSeeder
//実行結果
INFO Seeding database.
これでモデルの追加と初期データの追加が完了しました。次にコントローラーを変更していきます。
コントローラーの変更
コントローラーもモデルを使用する仕様に変更します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Bookmark;
class BookmarkController extends Controller
{
//
public function index ()
{
return Inertia::render('Bookmark/Index',['bookmarks' => Bookmark::all()]);
}
//検索メソッド
public function search ($queryWord)
{
return Inertia::render('Bookmark/Index',[
'bookmarks' => Bookmark::Where(
'title', 'like', '%'.$queryWord.'%'
)->orWhere(
'url', 'like', '%'.$queryWord.'%'
)->get()
]);
}
//追加メソッド
public function store (Request $request)
{
$bookmark = new Bookmark;
$bookmark->title = $request->title;
$bookmark->url = $request->url;
$bookmark->save();
return redirect()->route('bookmark.index', $parameters = [], $status = 303, $headers = []);
}
//削除メソッド
public function destroy ($id)
{
Bookmark::destroy($id);
return redirect()->route('bookmark.index', $parameters = [], $status = 303, $headers = []);
}
}
ルートの追加
この3つのルートを新たに追加します。検索と追加、削除用のルートです。
//下の3行を追加
Route::get('/Bookmark/search/{queryWord}',[App\Http\Controllers\BookmarkController::class, 'search'])->where('queryWord', '.*')->name('bookmark.search');
Route::post('/Bookmark/store',[App\Http\Controllers\BookmarkController::class, 'store'])->name('bookmark.store');
Route::delete('/Bookmark/delete/{id}',[App\Http\Controllers\BookmarkController::class, 'destroy'])->name('bookmark.delete');
Inertia Linkの実例
検索ボタンの変更
検索ボタンです。
- as属性にbuttonを指定してボタンにします。
- method属性にgetを指定してGETリクエストを行います。
- :href属性でリンク先を指定します。名前付きルートを使用して引数に検索ワードを渡しています。
- preserve-state属性で、フォームの内容がページ読み込み時に残るようにしています。
<input type=text name=search v-model="searchWord" size=15 class="p-1 m-1 text-sm" />
<Link as="button" method="get" :href="route('bookmark.search',{queryWord: searchWord})" preserve-state class="border border-gray-400 m-1 p-1 text-sm">ブックマーク検索</Link>
ブックマーク追加ボタン
ブックマーク追加するために、タイトルとURLを入力するボックスと追加ボタンを追加しました。
追加ボタンはこのような感じです。
- method属性で、POSTリクエストにしています。
- data属性で、送信するデータを指定しています。titleに新規タイトル、urlに新規URLを渡しています。
<Link as="button" method="post" :href="route('bookmark.store')" :data="{title: newTitle, url: newUrl}" class="border border-gray-400 m-1 p-1 text-sm">ブックマーク追加</Link>
ブックマーク削除ボタン
各ブックマークのリストの右側にブックマーク削除用のボタンを追加しました。
- method属性で、DELETEリクエストにしています。
- only属性をつけて、応答データとして「bookmarks」だけを読み込むように指定しています。
<Link as="button" method="delete" :href="route('bookmark.delete',bookmark.id)" preserve-scroll :only="['bookmarks']" class="border border-red-400 m-1 p-1 text-sm text-red-400">削除</Link>
only属性をつけた時の実際の挙動
only属性をつけると部分的なデータを読み込むというのがいまいちピンとこない方のために、only属性をつけた場合と付けない場合とでどう変わるのかをデベロッパーツールで見てみます。
このように、only属性をつけると通信量の節約になります。ただこれだけではLaravel側でデータは取得しているので、サーバー側の負荷は変わっていません。データ自体を取得しないようにするにはコントローラー側でもう一工夫必要になります。
//これだと毎回取得してしまう
return Inertia::render('Bookmark/Index',['bookmarks' => Bookmark::all()]);
//これで必要な時だけ取得するようになる
return Inertia::render('Bookmark/Index',['bookmarks' => fn() => Bookmark::all()]);
サンプルソース
削除や追加ボタンを加えた「Index.vue」はこのようになります。
<script setup>
import { ref } from "vue";
import { Link } from '@inertiajs/vue3'
import Layout from '@/Pages/Layout.vue'
defineProps({ bookmarks: Array })
const newTitle = ref('');
const newUrl = ref('');
</script>
<template>
<Layout title="ブックマーク一覧">
<h2 class="text-lg border-b p-2">
<Link :href="route('bookmark.index')" class="text-blue-700 underline">
ブックマーク一覧
</Link>
</h2>
<ul class="list-disc list-inside p-2">
<li v-for="bookmark in bookmarks" class="list-none flex justify-between">
<a :href="bookmark.url" target=_blank class="text-blue-700" >{{ bookmark.title }}</a>
<Link as="button" method="delete" :href="route('bookmark.delete',bookmark.id)" preserve-scroll :only="['bookmarks']" class="border border-red-400 m-1 p-1 text-sm text-red-400">削除</Link>
</li>
</ul>
<h3 class="border-l-4 p-1">ブックマーク追加</h3>
<div class="flex w-60">
<div class="w-20 text-right bg-gray-200 text-sm p-1 m-1">タイトル</div>
<div class="w-40">
<input type=text name=newTitle v-model="newTitle" size=15 class="p-1 m-1 text-sm w-full border" />
</div>
</div>
<div class="flex w-60">
<div class="w-20 text-right bg-gray-200 text-sm p-1 m-1">URL</div>
<div class="w-40">
<input type=text name=newUrl v-model="newUrl" size=15 class="p-1 m-1 text-sm w-full border" />
</div>
</div>
<Link as="button" method="post" :href="route('bookmark.store')" :data="{title: newTitle, url: newUrl}" class="border border-gray-400 m-1 p-1 text-sm">ブックマーク追加</Link>
</Layout>
</template>
「Layout.vue」はこのようになります。
<script setup>
import { ref } from "vue";
import { Link, Head } from '@inertiajs/vue3'
defineProps({ title: String })
const searchWord = ref('');
</script>
<template>
<Head :title="title" />
<main>
<header class="bg-gray-100">
<Link href="/" class="text-blue-700 underline m-2">Home</Link>
<Link href="/HelloWorld" class="text-blue-700 underline m-2">HelloWorld</Link>
<Link :href="route('bookmark.index')" class="text-blue-700 underline m-2">ブックマーク一覧</Link>
<input type=text name=search v-model="searchWord" size=15 class="p-1 m-1 text-sm" />
<Link as="button" method="get" :href="route('bookmark.search',{queryWord: searchWord})" preserve-state class="border border-gray-400 m-1 p-1 text-sm">ブックマーク検索</Link>
</header>
<article class="max-w-screen-sm">
<slot />
</article>
</main>
</template>
完全なソース一覧はGitHubからご覧ください。
Comments