Laravel + Inertia.js + Vue 3(TypeScript)の構成を学習していて、
interface Props と defineProps<Props>() の意味がいまいち
つかめなかったので、
MVCの流れと合わせて整理してみました。
1:Inertia環境とMVC
従来のMVCでは「ControllerがView(HTML)をレンダリングして返す」形でした。
Inertia.jsを使うと、VueコンポーネントがそのままViewの役割を持ち、
LaravelのControllerと直接データをやり取りします。
Inertia.jsって何だっけ
LaravelとVueを「APIを作らずに」つなげてくれる仕組み。
ControllerからVueコンポーネントへ、直接データを渡せる。
データの流れをMVCに当てはめるとこうなります。
M(Model):データを取得する
Laravel: Product.php
productsテーブル(id, name, price, stock, description)を扱う
// Laravel: Product.php
// productsテーブル(id, name, price, stock, description)その他を扱う
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
protected $fillable = [
'name',
'price',
'stock',
'description',
'created_at',
'updated_at',
];
}
C(Controller):ViewへデータをPropsとして渡す
// Laravel: ProductController.php
public function index()
{
return Inertia::render('Products/Index', [
// このキー名が、Vue側の props.products になる
'products' => Product::all(),
]);
}
V(View):受け取って表示する
<script setup lang="ts">
interface Props {
products: Array<{
id: number;
name: string;
price: number;
stock: number;
description: string;
}>;
}
const props = defineProps<Props>();
</script>
まとめると
Model → DBのデータ
Controller → Inertia::render() でPropsとして渡す
View(Vue) → defineProps() で受け取って表示
Controllerの 'products' とVueの props.products が
同じ名前で直接つながるのがポイント
2:interface Props { ... }の意味
これは「このコンポーネントが受け取るデータの説明書(設計図)」です。
interface Props {
products: Array<{ ... }>;
}
| 部分 | 意味 |
|---|---|
| products | 受け取るデータの名前(プロパティ名) |
| Array<...> | データが配列(リスト)で届く |
| { id: number; ... } | 配列の中身(商品1個分)の構造 |
Product::all() や Product::get() は
複数の商品の集まりを返すため Array にする必要があります。
各項目の型
-
id→ number -
name→ string -
price→ number -
stock→ number -
description→ string
こんな感じ。 descriptionは
文字数が多いけどstring型で定義してOK。
3:const props = defineProps();の意味
さっき作った説明書を、実際にVueに適用している部分です。
-
defineProps:「親からデータ(Props)を受け取ります」と
いう合図(Vue 3の<script setup>用) -
<Props>:さっきのinterface Propsのルールを適用する(ジェネリクスという) -
const props =:受け取ったデータをpropsに入れて使えるようにする
これで、テンプレート内で props.products としてアクセスできます。
<template>
<ul>
<li v-for="product in props.products" :key="product.id">
{{ product.name }}:{{ product.price }}円
</li>
</ul>
</template>
4:型定義をすると何が良いの?
① エディタが補完してくれる
v-for="product in props.products" の後で product.〜 と打つと、
id / name / price / stock / description が候補に出ます。
② タイポにすぐ気づける
Laravel側は price で送っているのに、Vue側で product.kakaku と
書いてしまった場合、
ブラウザで動かす前にエディタが赤線でエラーを教えてくれます。
5:Productは切り出すべき?それともPropsで定義するべき?
結論:どっちでもいい
今の書き方(インライン定義)はそのままでOKだが、
切り出すのは「ダメだから」ではなく「あとで楽をしたいから(再利用)」
① インライン定義するパターン(今採用してるやつ)
interface Props {
products: Array<{
id: number;
name: string;
price: number;
stock: number;
description: string;
}>;
}
インライン定義とは
v-on ディレクティブを使用することで、 DOM イベントの購読やイベント発火時にいくつかの JavaScript を実行します。これは通常 @ に省略することができます。使い方は v-on:click="handler"、あるいは省略して @click="handler" として使用します。
参考:vue公式ドキュメント https://ja.vuejs.org/guide/essentials/event-handling
② Product を外に切り出すパターン
interface Product {
id: number;
name: string;
price: number;
stock: number;
description: string;
}
interface Props {
products: Product[]; // Array<Product> でも同じ意味
}
| インライン | 切り出し | |
|---|---|---|
| メリット | このファイルだけで「何が届くか」が分かる | 他の場所でも型を使い回せる |
| デメリット | 他で使い回しにくい | 行数が少し増える |
例えば、「商品をクリックしたら詳細をモーダルで表示したい」機能を足すとき
切り出していれば関数の引数にそのまま使えます。
const openModal = (product: Product) => {
selectedProduct.value = product;
isModalOpen.value = true;
};
切り出していないと、引数の型を長々と書き直すか
any に頼ることになってしまいます。
(でもanyだとTSエラー出ちゃう)
- 今のファイル以外で商品の構造を使う予定がない → 今のままでOK
- 別の関数・子コンポーネント・編集画面でも使う
→interface Productとして切り出す
注意点
型定義は「Laravelの中身」を自動でチェックしてくれるわけではありません。
interface はあくまでVue側に自分で書いた「期待するデータの形」で
Laravel側のカラム(型)を変えたときは、こちらも手直しが必要です。
たとえばDBの decimal カラムは、実際には文字列で届くことがあります。
その場合 price: number と書いていても、実データは
string というズレが起こりえます。
また、interface を別ファイルに置いて import する場合は、
Vue 3.3以降であれば defineProps で使えます。
まとめ
- Inertiaでは**Vueコンポーネントが「MVCのV」**になり、
ControllerからPropsで直接データが届く -
interface Propsは受け取るデータの説明書、
defineProps<Props>()はそれを適用する処理 - 型定義のおかげで、補完とタイポの検出が効く
- 他でも使い回すなら
Productを切り出す
使わないなら今のままでOK - ただし型はあくまで「自分で書いた約束」のため
Laravel側の変更とはズレる可能性がある
MVCの全体像とつなげて考えると、型定義の意味がだいぶスッキリしました。