こちらは スタジオ・アルカナ Advent Calendar 2024 の3日めの記事になります!
はじめに
はじめまして! スタジオアルカナのエンジニア、今山と申します。
Qiitaには同姓同名で顔もスキルセットも同じな人がいますが諸事情ありまして便宜上別人ということにさせてください。大人の世界には色々あるのです。
さて、スタジオアルカナではPHP/Laravelを使用した開発が主流となっております。
昔はもっと色々やってたみたいなんですが、現在はこれが多いです。
で、LaravelといえばMVCのV(View)のところがBladeという、独自のテンプレートエンジンによって実現されているのが有名かと思います。
テンプレートエンジンといえばかつて世間を圧倒したSmarty、Twigなどがありますが、Bladeもシンプル&高機能で大変重宝されれるものでした。というか、今もメインストリームの一つであります。
ただ、最近はReactやVueやSveltと言ったJavaScriptで作られたJS版テンプレートエンジン(フロントエンドフレームワークっていうっぽいのですが)のようなものも増えてきてまして、リッチなUIを実現するうえでBladeはキツくなってきました。
jQueryやAlpineJSを使って作ることもできますが、コードの再利用が難しく、とんでもない記述量になってしまうこともしばしばです。
それならReactやVueでバシバシにコンポーネント分割して松花堂弁当のように組んでしまうのがエコでSDGsにも適うというものです。
だんだん前置きが胡散臭くなってきたので、そろそろ本題に入りましょう。
InertiaJSとは
「イナーシャジェーエス」と読みます。スペルはtですが、発音はsなんだそうです。
LaravelやRuby on RailsのフロントエンドエンジンをReactやVueに置き換えてくれる素敵な仕組みです。
結局のところ、 app.blade.php
上でトランスパイル済みの app.js
を呼び出してるだけなので、完全にBladeから脱却できるってわけでもないのですが、基本的にBladeを書くことはなくなります。
まだInertiaがなかった頃、VueやReactを使おうとしたらコンポーネントのpropsに @json
で渡したりとか、REST APIを作ってブラウザとサーバー間で通信できるようにする必要がありました。
インタラクティブなUI作ろうと思ったらそうするしかなかったのでそうしてたわけなんですが、ページに値を渡すところやらREST API使ってデータのやり取りするところとか全部面倒見てやんよ!ってのがInertiaなわけです。
でも難しいんでしょう?
HTMLでバリバリ書けたBladeとは違って、前述の通りReactやVueやSveltの知識が必要……ってわけでもないです。
始めるだけだったら同じです。
難しいところはおいおい覚えていけばいいのです。
まずはBladeとおんなじようなことができるところまで行きましょう。
はじめ方そのものはここを見てください。Laravel Breezeを使うのが一番手っ取り早いです。
Bladeの代わりにReactやVueで書いてみよう
では一例として、僕が応援してるJリーグチーム、 ジェフユナイテッド市原千葉
がクラブ史上最高得点を叩き出した4/3のホーム戦の得点記録を出力するページを作ってみましょう。
参考:
これをBladeで表示させるControllerを書くとこんな感じになると思います。
class HighScoreController extends Controller
{
public function __invoke()
{
$goals = [
[
'time' => '3',
'player' => '小森飛絢'
],
[
'time' => '25',
'player' => '鈴木大輔'
],
[
'time' => '43',
'player' => '椿直起'
],
[
'time' => '50',
'player' => '高橋壱晟'
],
[
'time' => '56',
'player' => '鈴木大輔'
],
[
'time' => '59',
'player' => 'ドゥドゥ'
],
[
'time' => '75',
'player' => 'ドゥドゥ'
],
[
'time' => '76',
'player' => '岡庭愁人'
],
];
return view('highscore', ['scores' => $scores]);
}
}
class HighScoreController extends Controller
{
public function __invoke()
{
$goals = [
[
'time' => '3',
'player' => '小森飛絢'
],
[
'time' => '25',
'player' => '鈴木大輔'
],
[
'time' => '43',
'player' => '椿直起'
],
[
'time' => '50',
'player' => '高橋壱晟'
],
[
'time' => '56',
'player' => '鈴木大輔'
],
[
'time' => '59',
'player' => 'ドゥドゥ'
],
[
'time' => '75',
'player' => 'ドゥドゥ'
],
[
'time' => '76',
'player' => '岡庭愁人'
],
];
return view('highscore', ['scores' => $scores]);
}
}
で、Blade側がこんな感じですよね。
@props(['goals'])
<div class='main'>
<h1>ジェフユナイテッド千葉2024年第8節SC栃木戦ゴール一覧</h1>
<table>
<thead>
<tr>
<th>選手</th>
<th>ゴール時間</th>
</tr>
</thead>
<tbody>
@foreach($goals as $goal)
<tr>
<td>{{ $goal->player }}</td>
<td>{{ $goal->time }}分</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@props(['goals'])
<div class='main'>
<h1>ジェフユナイテッド千葉2024年第8節SC栃木戦ゴール一覧</h1>
<table>
<thead>
<tr>
<th>選手</th>
<th>ゴール時間</th>
</tr>
</thead>
<tbody>
@foreach($goals as $goal)
<tr>
<td>{{ $goal->player }}</td>
<td>{{ $goal->time }}分</td>
</tr>
@endforeach
</tbody>
</table>
</div>
じゃあ、これをInertiaで書くとどうなるかというと、Controller側は return
を view
から inertia
にするだけです。
Inertiaのサイトには Inertia::render()
で書かれてますが、これでちゃんと動きます。
昔のLaravelがViewを出力するときに View::render()
って書かせてたかと思ったら view()
ってヘルパ関数になったのと同じですね。
class HighScoreController extends Controller
{
public function __invoke()
{
$goals = [
// 略
];
return inertia('highscore', ['scores' => $scores]);
}
}
class HighScoreController extends Controller
{
public function __invoke()
{
$goals = [
// 略
];
return inertia('highscore', ['scores' => $scores]);
}
}
これをReactのtsxで表示するとどうなるか。こうです。
highscore.tsx
type Goal = {
player: string;
time: number;
}
export default function highscore({ goals }: { goals: Goal[] }) {
return (
<div class='main'>
<h1>ジェフユナイテッド千葉2024年第8節SC栃木戦ゴール一覧</h1>
<table>
<thead>
<tr>
<th>選手</th>
<th>ゴール時間</th>
</tr>
</thead>
<tbody>
{goals.map((goal) => (
<tr>
<td>{goal.player}</td>
<td>{goal.time}分</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
type Goal = {
player: string;
time: number;
}
export default function highscore({ goals }: { goals: Goal[] }) {
return (
<div class='main'>
<h1>ジェフユナイテッド千葉2024年第8節SC栃木戦ゴール一覧</h1>
<table>
<thead>
<tr>
<th>選手</th>
<th>ゴール時間</th>
</tr>
</thead>
<tbody>
{goals.map((goal) => (
<tr>
<td>{goal.player}</td>
<td>{goal.time}分</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Vueならこうですね。
highscore.vue
<script setup>
type Goal = {
player: string;
time: number;
}
defineProps({
goals: Goal[]
})
</script>
<template>
<div class='main'>
<h1>ジェフユナイテッド千葉2024年第8節SC栃木戦ゴール一覧</h1>
<table>
<thead>
<tr>
<th>選手</th>
<th>ゴール時間</th>
</tr>
</thead>
<tbody>
<tr v-for="goal in goals">
<td>{{goal.player}}</td>
<td>{{goal.time}}分</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
type Goal = {
player: string;
time: number;
}
defineProps({
goals: Goal[]
})
</script>
<template>
<div class='main'>
<h1>ジェフユナイテッド千葉2024年第8節SC栃木戦ゴール一覧</h1>
<table>
<thead>
<tr>
<th>選手</th>
<th>ゴール時間</th>
</tr>
</thead>
<tbody>
<tr v-for="goal in goals">
<td>{{goal.player}}</td>
<td>{{goal.time}}分</td>
</tr>
</tbody>
</table>
</div>
</template>
こうしてみると思ったほど差がねえな、って思われるかと思います。
次回はどっかでFormを利用したデータのやり取りを書きます。
ここで APIを作らなくていい の意味を知ることになると思います。
お楽しみに!