これじゃ型のアドベントカレンダーじゃないか!
ということで実践編です。
正直型問題をクリアしないと vilt スタック微妙なんですよ。
OpenAPI 使ってフロントバック分けたほうが見通しが良い。。。
逆に型問題をクリアした VILT スタックは強すぎます。現環境最強?です。
以下 React でもできるんですが、私が Vue 信者なので Vue ですすめますね。
ページの表示
基本的には Laravel の Blade と似た感じで運用することができます。
試しに GET /test というルートに、ユーザーデータを渡す処理を作ってみます。
TestController.php は
__invoke()で作っても良き。
use App\Http\Controllers\TestController;
use Illuminate\Support\Facades\Route;
Route::get('/test', [TestController::class, 'index']);
コントローラーでは、通常の処理と同じように書きますが、 return にて Inertia::render() を返却します。
これは Blade の view() と同じ仕組みで、第一引数に View 、第二引数に渡す変数を指定することができます。
ビューは resources/js/pages/XXX/YYY.vue であれば XXX/YYY と記載すればOKです。
変数は自動的に JSON 化して渡されます。
変数は
compact()を使うのも良き。
namespace App\Http\Controllers;
use App\Models\User;
use Inertia\Inertia;
class TestController extends Controller
{
public function index()
{
$users = User::limit(5)->get();
return Inertia::render('Test', [
'users' => $users,
]);
}
}
その値をどうやって受け取るのかというと、コンポーネントのように defineProps() でそのまま受け取ることができます。
ここで前述の記事で自動生成したモデルの型を使うことで、何の苦労もなくフロントとバックを繋ぐことができます。
これが VILT スタック の API です。
<template>
<table class="m-4 border-collapse border-2">
<thead>
<tr>
<th class="p-2 border-2">ID</th>
<th class="p-2 border-2">Name</th>
<th class="p-2 border-2">Email</th>
</tr>
</thead>
<tbody>
<tr v-for="user of users" :key="user.id">
<td class="p-2 border-2">{{ user.id }}</td>
<td class="p-2 border-2">{{ user.name }}</td>
<td class="p-2 border-2">{{ user.email }}</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
defineProps<{
users: App.Models.User[],
}>()
</script>
ででん!
こんな感じで繋げるため Vue 側には Router がありません。
Layout の付け方
じゃぁ Layout ってどうやって使うの?ってなると思います。
それは defineOptions() で定義することができます。
layouts ディレクトリを js 配下に作って、レイアウトファイルを作成します。
<template>
<div class="m-2 bg-blue-950">
<slot></slot>
</div>
</template>
それをページルートの defineOptions() に読ませばOKです。
// ...
<script setup lang="ts">
import BaseLayout from '@/layouts/BaseLayout.vue'
defineOptions({
layout: BaseLayout
})
// ...
defineOptions() は最近できたマクロなんですが、 inheritAttrs とかもかける便利な奴です。
わざわざ <script> を書かなくて済むので神です。
データ共有(疑似セッション)
もう一つ共有の方法があります。
いわゆる session() ですね。
ただ Inertia を利用していると Laravel の Session はそのまま使えません...。
疑似 SPA の構成なので、セッションの再読み込みがかかったり、かからなかったり...
この手法を知るまで謎のバグに会い続けていた...。
デフォルトでは HandleInertiaRequests というミドルウェアが用意されていて、こちらを使うことでフロントと値を共有することができます。
public function share(Request $request): array
{
[$message, $author] = str(Inspiring::quotes()->random())->explode('-');
return [
...parent::share($request),
'name' => config('app.name'),
'quote' => ['message' => trim($message), 'author' => trim($author)],
'auth' => [
'user' => $request->user(),
],
];
}
デフォルトだとこんな感じのものが書かれていると思います。
簡単に言うと、こちらに指定したものが json となって毎通信フロントに渡ります。
quoteはサンプル用に用意された、偉人の言葉が返ってくる変数になってます。
ここで定義したオブジェクトは、フロント側にあらかじめ定義されている AppPageProps と型を合わせてください。
ちなみに User は App.Models.User に置き換えると、自動型生成のサイクルに組み込むことができて幸せになります。
export interface Auth {
user: User;
}
export type AppPageProps<T extends Record<string, unknown> = Record<string, unknown>> = T & {
name: string;
quote: { message: string; author: string };
auth: Auth;
};
Inerita のフロントには usePage() という composable が存在します。
この中の props に上記値が入っています。
ためしに quote を取り出してみると、
<template>
<pre class="m-4">{{ page.props.quote }}</pre>
</template>
<script setup lang="ts">
import { usePage } from '@inertiajs/vue3'
const page = usePage()
// ...
</script>
こんな感じで取り出すことができます。
ちゃんと TypeHint も出るので、とても楽ですね。
Share はページと関係なく常に同期したい、Laravel の設定情報や、認証ユーザーの情報、トーストの通知情報など、で利用すると良いです。
ただ毎通信のペイロードに乗るため、乗せすぎは注意です。
遅延取得
Inertia 全般に言えるのですが、値をフロントに渡す際に fn () でくるんで関数を渡すことができます。
fn () でくるんでおくと、必要な時に関数を実行して値を取り出して、取り出してくれる computed のような挙動をします。
何か挙動がおかしいと思ったときは、この fn () が足りていないことが多いので、つけてみましょう。
[
'normal' => User::get(),
'wrap' => fn () => User::get(),
]
詳しくは割愛しますが、Inertia は部分取得することが多いので、覚えておきましょう。


