Laravel Livewire入門|導入手順からコンポーネント作成・フォーム検証まで徹底解説

Laravel入門

「Laravel Livewireとは何か」「Vue.jsやReactを使わずに動的な画面を作りたい」「どう導入してどう書けばいいのか」と悩んでいませんか?

Laravel Livewire(ライブワイヤー)は、PHPとBladeテンプレートの知識だけで、SPA(シングルページアプリケーション)のようなリアクティブで動的なWebインターフェースを構築できるLaravel公式のフルスタックフレームワークです。

本記事では、Livewireの基本的な仕組みやInertia.js・フロントエンドフレームワークとの違い、Composerでのインストール手順から、カウンター作成、データバインディング、モダンなフォームバリデーション(#[Validate]属性)、イベント連携、テスト手法、実務での注意点までを体系的に解説します。

  1. Laravel Livewireとは?仕組みと選ばれる理由
    1. Livewireが動く仕組み(AjaxとDOM Morphing)
    2. Livewire vs Inertia.js vs Vue/React vs 従来Blade の違い
  2. Laravel Livewireのインストールと環境構築
    1. 1. Composerでパッケージをインストール
    2. 2. レイアウトファイルの確認とアセットの配置
  3. はじめてのLivewireコンポーネント作成(カウンター)
    1. 1. make:livewire コマンドで生成
    2. 2. PHPクラスの実装
    3. 3. Bladeビューの実装
    4. 4. コンポーネントの呼び出し方法
    5. フルページコンポーネントとしてのルーティング
  4. データバインディングと wire:model の修飾子
    1. wire:model の同期タイミングの違い
    2. 通信中のローディング表示(wire:loading)
  5. 実践:リアルタイムバリデーション付きフォームの実装
    1. PHPクラスの実装(ContactForm.php)
    2. Bladeビューの実装(contact-form.blade.php)
  6. コンポーネント間のイベント連携(ディスパッチとリスナー)
    1. イベントの発行側
    2. イベントの購読側(#[On] アトリビュート)
  7. 計算プロパティ(#[Computed])で無駄なクエリを防ぐ
  8. Livewireコンポーネントのテスト手法
  9. Livewire導入でつまずきやすい注意点と解決策
    1. 1. Bladeビューのルート要素は必ず1つにする
    2. 2. ループ処理(@foreach)には必ず wire:key を設定する
    3. 3. publicプロパティのセキュリティ(改ざん対策)
  10. よくある質問(FAQ)
    1. Q1. LivewireとAlpine.jsはどう使い分ければいいですか?
    2. Q2. LivewireはSEOに影響がありますか?
    3. Q3. Laravel Filamentとの関係は?
  11. 関連記事
  12. まとめ
  13. 関連記事

Laravel Livewireとは?仕組みと選ばれる理由

Laravel Livewireは、サーバーサイドレンダリング(SSR)の堅牢さと、SPAのような滑らかな操作感を両立するライブラリです。JavaScriptフレームワーク(Vue.jsやReactなど)を別途導入することなく、慣れ親しんだBladeビューとPHPクラスだけでリアルタイムなUIを構築できます。

Livewireが動く仕組み(AjaxとDOM Morphing)

Livewireコンポーネントがブラウザで操作されたとき、内部では次のようなサイクルが高速に処理されています。

  1. 初回レンダリング: サーバー側で通常のBladeテンプレートとしてHTMLが生成され、ブラウザに描画されます(SEOや初期表示速度に優れています)。
  2. イベント検知: ボタンクリックやテキスト入力(wire:clickwire:model)が発生すると、Livewireのフロントエンドスクリプトがそれをキャプチャします。
  3. 非同期リクエスト(Ajax/Fetch): 変更されたプロパティ値や実行したいメソッド名を含むJSONリクエストがサーバーへ送信されます。
  4. サーバー処理と再レンダリング: サーバー側でPHPコンポーネントの状態が更新され、新しいHTMLが再レンダリングされます。
  5. DOM差分更新(Morphing): ブラウザ側で新旧のHTMLが比較され、変更のあった要素だけがピンポイントで書き換えられます。

この仕組みにより、ページ全体の再読み込み(リロード)を発生させずに、画面の一部だけをスムーズに更新できます。

Livewire vs Inertia.js vs Vue/React vs 従来Blade の違い

Laravelでフロントエンドを構築する際の選択肢を比較表にまとめました。

手法 主要言語 APIの作成 画面描画(レンダリング) 適しているケース
Livewire PHP (Blade) 不要 サーバーサイド + DOM差分更新 Laravel開発者が素早く動的UIや管理画面を作りたい場合
Inertia.js PHP + Vue / React 不要(コントローラー直結) クライアントサイド (SPA) VueやReactのリッチなコンポーネント資産を活用したい場合
SPA (Vue / React独立) JavaScript / TypeScript 必須 (REST / GraphQL) クライアントサイド モバイルアプリとAPIを共用する大規模サービス
従来のBlade PHP (Blade) 不要 サーバーサイド(都度全画面遷移) 静的な情報発信サイトやシンプルなWebページ

「JavaScriptのビルド環境やAPI設計の管理コストを抑えつつ、インタラクティブなUIを素早く実装したい」という場面において、Livewireは最も生産性の高い選択肢です。

Laravel Livewireのインストールと環境構築

LivewireはComposerを使って簡単に導入できます。Laravel 10 / 11 / 12 / 13、PHP 8.1 / 8.2 / 8.3 / 8.4 に対応しています。

1. Composerでパッケージをインストール

composer require livewire/livewire

Laravelのパッケージ自動検出(Package Discovery)が働くため、config/app.phpの編集などは不要です。

2. レイアウトファイルの確認とアセットの配置

Livewire 3系以降では、Bladeテンプレートに記述されたLivewireコンポーネントを検出してスクリプト(Alpine.jsおよびLivewireアセット)が自動注入されます。

明示的にレイアウトファイルを用意したい場合は、以下のArtisanコマンドでベースレイアウトを生成します。

php artisan livewire:layout

生成されるresources/views/components/layouts/app.blade.phpの基本構成は以下の通りです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ $title ?? 'Laravel Livewire App' }}</title>
    @livewireStyles
</head>
<body>
    {{ $slot }}

    @livewireScripts
</body>
</html>

※Livewire 3以降は@livewireStyles@livewireScriptsを省略しても自動注入されますが、カスタムアセットやスタイル読み込み位置を細かく制御したい場合は明示的に記述しておくと安心です。

はじめてのLivewireコンポーネント作成(カウンター)

Livewireの基本構造を理解するために、ボタンを押すと数値が増減するシンプルなカウンターコンポーネントを作成してみましょう。

1. make:livewire コマンドで生成

php artisan make:livewire Counter --class

このコマンドにより、ロジックを担当するPHPクラスと表示を担当するBladeテンプレートの2ファイルが生成されます。

  • PHPクラス: app/Livewire/Counter.php
  • Bladeビュー: resources/views/livewire/counter.blade.php

※Livewire 2系までのApp\Http\Livewireから、Livewire 3系以降はApp\Livewireへ名前空間が変更されています。

2. PHPクラスの実装

コンポーネントの公開プロパティ(public変数)は、自動的にBladeビューとブラウザ間で共有されます。

app/Livewire/Counter.php

<?php

namespace App\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }

    public function decrement(): void
    {
        if ($this->count > 0) {
            $this->count--;
        }
    }

    public function render()
    {
        return view('livewire.counter');
    }
}

3. Bladeビューの実装

ボタンのクリックイベントにはwire:clickディレクティブを指定します。

resources/views/livewire/counter.blade.php

<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md text-center">
    <h2 class="text-2xl font-bold mb-4">現在のカウント: {{ $count }}</h2>
    <div class="space-x-2">
        <button wire:click="decrement" class="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400">-</button>
        <button wire:click="increment" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">+</button>
    </div>
</div>

4. コンポーネントの呼び出し方法

作成したLivewireコンポーネントは、他のBladeテンプレートからカスタムタグまたはディレクティブで呼び出せます。

<!-- 推奨されるタグ形式 -->
<livewire:counter />

<!-- または Bladeディレクティブ形式 -->
@livewire('counter')

フルページコンポーネントとしてのルーティング

コントローラーを挟まず、ルーティング(routes/web.php)に直接Livewireクラスを指定して1ページとして表示することも可能です。

use App\Livewire\Counter;
use Illuminate\Support\Facades\Route;

Route::get('/counter', Counter::class);

データバインディングと wire:model の修飾子

フォーム入力値とPHPプロパティを同期させるにはwire:modelを使用します。Livewire 3以降で最も重要な仕様変更の1つが、データ同期のタイミングです。

wire:model の同期タイミングの違い

  • wire:model (遅延同期 / デフォルト): 入力中はその都度通信せず、フォームの送信(wire:submit)や他のアクションがトリガーされたタイミングでまとめてサーバーに同期されます。無駄な通信を削減し、高速に動作します。
  • wire:model.live (即時同期): 入力文字が変更されるたびに即座にサーバーへ通信してプロパティを更新します(インクリメンタルサーチやリアルタイムバリデーション向け)。
  • wire:model.blur: フォーカスが外れた(onblur)タイミングでのみ同期します。
  • wire:model.live.debounce.500ms: キーストローク後に指定時間(500msなど)入力が止まってから通信を送信します。
<!-- フォーム送信時に同期 -->
<input type="text" wire:model="title">

<!-- 入力停止後300msでリアルタイム同期 -->
<input type="text" wire:model.live.debounce.300ms="searchQuery">

<!-- フォーカスが外れたときに同期 -->
<input type="email" wire:model.blur="email">

通信中のローディング表示(wire:loading)

非同期通信中に「読み込み中…」スピナーを表示したりボタンを非活性化するには、wire:loadingを活用します。

<button type="submit" wire:loading.attr="disabled" class="btn btn-primary">
    <span wire:loading.remove>送信する</span>
    <span wire:loading>送信処理中...</span>
</button>

<!-- 特定のアクション実行中のみ表示 -->
<div wire:loading wire:target="savePost" class="text-blue-500">
    記事を保存しています...
</div>

実践:リアルタイムバリデーション付きフォームの実装

Livewire 3では、PHP 8のアトリビュート構文である#[Validate]属性を使って、極めて直感的にバリデーションルールを記述できます。

PHPクラスの実装(ContactForm.php)

app/Livewire/ContactForm.php

<?php

namespace App\Livewire;

use Livewire\Attributes\Validate;
use Livewire\Component;

class ContactForm extends Component
{
    #[Validate('required|min:2', message: 'お名前は2文字以上で入力してください')]
    public string $name = '';

    #[Validate('required|email', message: '有効なメールアドレスを入力してください')]
    public string $email = '';

    #[Validate('required|min:10', message: 'お問い合わせ内容は10文字以上で入力してください')]
    public string $message = '';

    public bool $isSuccess = false;

    // リアルタイムバリデーション(プロパティ更新時に検証)
    public function updated($propertyName): void
    {
        $this->validateOnly($propertyName);
    }

    public function submit(): void
    {
        // 全項目のバリデーション
        $this->validate();

        // データベース保存やメール送信処理
        // Mail::to('admin@example.com')->send(...);

        $this->reset(['name', 'email', 'message']);
        $this->isSuccess = true;
    }

    public function render()
    {
        return view('livewire.contact-form');
    }
}

Bladeビューの実装(contact-form.blade.php)

resources/views/livewire/contact-form.blade.php

<div class="max-w-lg mx-auto p-6 bg-white rounded-lg shadow">
    @if ($isSuccess)
        <div class="p-4 mb-4 text-green-700 bg-green-100 rounded">
            お問い合わせを送信しました。ありがとうございます!
        </div>
    @endif

    <form wire:submit="submit" class="space-y-4">
        <div>
            <label class="block font-medium">お名前</label>
            <input type="text" wire:model.blur="name" class="w-full border rounded p-2">
            @error('name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
        </div>

        <div>
            <label class="block font-medium">メールアドレス</label>
            <input type="email" wire:model.blur="email" class="w-full border rounded p-2">
            @error('email') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
        </div>

        <div>
            <label class="block font-medium">お問い合わせ内容</label>
            <textarea wire:model.blur="message" rows="4" class="w-full border rounded p-2"></textarea>
            @error('message') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
        </div>

        <button type="submit" class="w-full py-2 px-4 bg-blue-600 text-white rounded hover:bg-blue-700">
            送信する
        </button>
    </form>
</div>

コンポーネント間のイベント連携(ディスパッチとリスナー)

Livewireでは、コンポーネント間でイベントを発行(dispatch)し、別のコンポーネントでそれを購読(#[On])することでスムーズに連携できます。

イベントの発行側

// 例: 投稿作成完了時にイベントを発火
public function createPost(): void
{
    $post = Post::create([...]);

    // イベント名 'post-created' とパラメータを発行
    $this->dispatch('post-created', postId: $post->id);
}

イベントの購読側(#[On] アトリビュート)

Livewire 3では、#[On]属性をメソッドに付与するだけでイベントをキャッチできます。

use Livewire\Attributes\On;
use Livewire\Component;

class PostList extends Component
{
    #[On('post-created')]
    public function refreshPosts($postId): void
    {
        // 新しい投稿が作成されたら一覧を再取得・再描画
        session()->flash('message', "新しい記事(ID: {$postId})が追加されました");
    }

    public function render()
    {
        return view('livewire.post-list', [
            'posts' => Post::latest()->get(),
        ]);
    }
}

計算プロパティ(#[Computed])で無駄なクエリを防ぐ

コンポーネントが再レンダリングされるたびに同じデータベースクエリが実行されるのを防ぐには、#[Computed]属性を使用します。

use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\User;

class UserDirectory extends Component
{
    public string $search = '';

    #[Computed(persist: true)]
    public function users()
    {
        return User::where('name', 'like', "%{$this->search}%")->paginate(10);
    }

    public function render()
    {
        return view('livewire.user-directory');
    }
}

Blade内では $this->users としてアクセスでき、同一リクエスト内での重複実行が自動的にキャッシュされます。

Livewireコンポーネントのテスト手法

Livewireは標準で強力なテストヘルパー(Livewire::test())を提供しており、ブラウザ(PlaywrightやSeleniumなど)を起動しなくても高速に動作確認が可能です。

以下はPest / PHPUnitでのテストコード例です。

<?php

use App\Livewire\Counter;
use App\Livewire\ContactForm;
use Livewire\Livewire;

test('カウンターコンポーネントが正常に描画されインクリメントできる', function () {
    Livewire::test(Counter::class)
        ->assertSet('count', 0)
        ->call('increment')
        ->assertSet('count', 1)
        ->assertSee('現在のカウント: 1');
});

test('フォーム送信時のバリデーションエラーを検証する', function () {
    Livewire::test(ContactForm::class)
        ->set('name', '')
        ->set('email', 'invalid-email')
        ->call('submit')
        ->assertHasErrors(['name', 'email']);
});

test('正しい入力値でお問い合わせ送信が成功する', function () {
    Livewire::test(ContactForm::class)
        ->set('name', '山田 太郎')
        ->set('email', 'yamada@example.com')
        ->set('message', 'Livewireの導入についての問い合わせです。')
        ->call('submit')
        ->assertHasNoErrors()
        ->assertSet('isSuccess', true)
        ->assertSee('お問い合わせを送信しました');
});

Livewire導入でつまずきやすい注意点と解決策

1. Bladeビューのルート要素は必ず1つにする

LivewireコンポーネントのBladeテンプレートは、最上位が単一のHTMLタグ(<div>など)で囲まれている必要があります。

<!-- ❌ NG: ルート要素が複数あるとDOM更新が崩れる -->
<h1>タイトル</h1>
<p>コンテンツ</p>

<!-- ⭕ OK: 単一のルート要素で囲む -->
<div>
    <h1>タイトル</h1>
    <p>コンテンツ</p>
</div>

2. ループ処理(@foreach)には必ず wire:key を設定する

リストの追加・並び替え・削除が発生する際、DOM要素の取り違えを防ぐために各要素へ一意なwire:keyを付与してください。

<ul>
    @foreach ($users as $user)
        <li wire:key="user-{{ $user->id }}">
            {{ $user->name }}
            <button wire:click="deleteUser({{ $user->id }})">削除</button>
        </li>
    @endforeach
</ul>

3. publicプロパティのセキュリティ(改ざん対策)

LivewireのpublicプロパティはブラウザにJSONとして送受信されるため、ユーザーによる改ざんが可能です。パスワードハッシュや機密データ、ユーザーが書き換えてはいけないIDはprotected / privateにするか、#[Locked]属性を付与して保護してください。

use Livewire\Attributes\Locked;

class UserProfile extends Component
{
    #[Locked]
    public int $userId; // ブラウザ側からの改ざんを拒否
}

よくある質問(FAQ)

Q1. LivewireとAlpine.jsはどう使い分ければいいですか?

A. ドロップダウンメニューの開閉やモーダルの表示・非表示、タブ切り替えなどサーバー通信を必要としない純粋なクライアント側のUI制御はAlpine.jsを使い、データベース操作やサーバー側バリデーションが絡む処理はLivewireを担当させるのがベストプラクティスです。LivewireにはAlpine.jsが標準で組み込まれているため、追加インストールなしで両者を組み合わせて使えます。

Q2. LivewireはSEOに影響がありますか?

A. 影響はありません。Livewireコンポーネントは初回のページ読み込み時にサーバー側で完全なHTMLとしてレンダリング(SSR)されるため、検索エンジンのクローラーも問題なくコンテンツをインデックスできます。

Q3. Laravel Filamentとの関係は?

A. Laravel Filamentは、LivewireおよびTailwind CSS、Alpine.js(いわゆるTALLスタック)をベースに構築されたLaravel最高峰の管理画面フレームワークです。Livewireの基本を理解しておくことで、Filamentのカスタムコンポーネント作成や高度なカスタマイズも容易になります。

関連記事

Livewireの各種コマンドや周辺機能については、以下の関連記事もぜひ参考にしてください。

まとめ

Laravel Livewireを導入することで、Vue.jsやReactのようなフロントエンド環境を別途構築することなく、BladeとPHPのスキルだけでリッチで高速なWebアプリケーションを開発できます。

まずはcomposer require livewire/livewireでインストールし、make:livewireによるシンプルなカウンターやフォームコンポーネントの作成から試してみてください。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント