0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Laravel × Inertia × Vue 3】Propsの型定義で「MVC」のつながりを整理してみた

0
Last updated at Posted at 2026-09-24

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の全体像とつなげて考えると、型定義の意味がだいぶスッキリしました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?