Laravel Livewireとは
Laravel Livewireは、PHPだけでインタラクティブなUIコンポーネントを作成できるフルスタックフレームワークです。通常、動的UIにはJavaScriptフレームワーク(Vue.js・React・Alpine.js)が必要ですが、Livewireを使うとPHPのクラスとBladeテンプレートだけでリアルタイムな画面更新が実現できます。
仕組みの概要
- ユーザーがフォームを入力・ボタンをクリック
- Livewireが差分をサーバーに送信(Ajax)
- PHPが処理してHTMLの差分を返す
- LivewireがDOM差分更新(ページ再読み込みなし)
インストール
composer require livewire/livewire
# スターターキット(Laravel Breeze + Livewire)
php artisan breeze:install livewire
最初のLivewireコンポーネント——リアルタイム検索
php artisan make:livewire ProductSearch
// app/Livewire/ProductSearch.php
<?php
namespace App\Livewire;
use App\Models\Product;
use Livewire\Component;
use Livewire\WithPagination;
class ProductSearch extends Component
{
use WithPagination;
public string $search = '';
public string $category = '';
public string $sortBy = 'name';
public string $sortOrder = 'asc';
// プロパティが変更されたらページをリセット
public function updatingSearch(): void
{
$this->resetPage();
}
public function updatingCategory(): void
{
$this->resetPage();
}
public function render()
{
$products = Product::query()
->when($this->search, fn($q) =>
$q->where('name', 'like', "%{$this->search}%")
->orWhere('description', 'like', "%{$this->search}%")
)
->when($this->category, fn($q) =>
$q->where('category', $this->category)
)
->orderBy($this->sortBy, $this->sortOrder)
->paginate(20);
$categories = Product::distinct()->pluck('category');
return view('livewire.product-search', compact('products', 'categories'));
}
public function sort(string $field): void
{
if ($this->sortBy === $field) {
$this->sortOrder = $this->sortOrder === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $field;
$this->sortOrder = 'asc';
}
}
}
Bladeテンプレート
{{-- resources/views/livewire/product-search.blade.php --}}
<div>
{{-- 検索・フィルタ --}}
<div class="flex flex-col sm:flex-row gap-4 mb-6">
<div class="flex-1">
<input
wire:model.live.debounce.300ms="search"
type="text"
placeholder="商品名・説明で検索..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:outline-none"
>
</div>
<select
wire:model.live="category"
class="px-4 py-2 border border-gray-300 rounded-lg"
>
<option value="">全カテゴリ</option>
@foreach($categories as $cat)
<option value="{{ $cat }}">{{ $cat }}</option>
@endforeach
</select>
</div>
{{-- ローディングインジケーター --}}
<div wire:loading class="text-center py-4 text-gray-500">
<svg class="animate-spin h-6 w-6 mx-auto" ...></svg>
</div>
{{-- 商品テーブル --}}
<div wire:loading.remove>
<table class="w-full text-sm">
<thead class="bg-gray-50">
<tr>
<th class="text-left p-3 cursor-pointer hover:bg-gray-100"
wire:click="sort('name')">
商品名
@if($sortBy === 'name')
{{ $sortOrder === 'asc' ? '▲' : '▼' }}
@endif
</th>
<th class="text-right p-3 cursor-pointer hover:bg-gray-100"
wire:click="sort('price')">
価格
@if($sortBy === 'price')
{{ $sortOrder === 'asc' ? '▲' : '▼' }}
@endif
</th>
<th class="text-left p-3">カテゴリ</th>
</tr>
</thead>
<tbody>
@forelse($products as $product)
<tr class="border-t hover:bg-gray-50">
<td class="p-3">{{ $product->name }}</td>
<td class="p-3 text-right">¥{{ number_format($product->price) }}</td>
<td class="p-3">{{ $product->category }}</td>
</tr>
@empty
<tr>
<td colspan="3" class="p-6 text-center text-gray-400">
商品が見つかりません
</td>
</tr>
@endforelse
</tbody>
</table>
<div class="mt-4">
{{ $products->links() }}
</div>
</div>
</div>
モーダルコンポーネント
// app/Livewire/ProductModal.php
class ProductModal extends Component
{
public bool $show = false;
public ?Product $product = null;
// 他のコンポーネントからのイベントをリッスン
#[On('open-product-modal')]
public function openModal(int $productId): void
{
$this->product = Product::find($productId);
$this->show = true;
}
public function closeModal(): void
{
$this->show = false;
$this->product = null;
}
public function render()
{
return view('livewire.product-modal');
}
}
{{-- モーダルのBladeテンプレート --}}
<div>
@if($show)
<div class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center"
wire:click.self="closeModal">
<div class="bg-white rounded-xl p-6 max-w-lg w-full mx-4 shadow-2xl">
<div class="flex justify-between items-start mb-4">
<h2 class="text-xl font-bold">{{ $product->name }}</h2>
<button wire:click="closeModal" class="text-gray-400 hover:text-gray-600">✕</button>
</div>
<p class="text-2xl font-bold text-blue-600">¥{{ number_format($product->price) }}</p>
<p class="text-gray-600 mt-2">{{ $product->description }}</p>
</div>
</div>
@endif
</div>
パフォーマンス最適化
// Lazy Loading(表示領域に入ったときに読み込む)
class HeavyDashboard extends Component
{
use WithoutUrlPagination;
public function placeholder(): string
{
return <<<HTML
<div class="animate-pulse bg-gray-200 h-64 rounded-xl"></div>
HTML;
}
public function render()
{
return view('livewire.heavy-dashboard');
}
}
{{-- Lazy Loading --}}
<livewire:heavy-dashboard lazy />
まとめ
Laravel Livewireは、JavaScriptの学習コストなしにリアルタイムUIを実装できる強力なツールです。検索フィルタ・ページネーション・モーダル・フォームバリデーションなど、よくある動的UI要件のほとんどをPHPだけで対応できます。弊社では予約管理システムの管理画面でLivewireを採用し、開発工数を大幅に削減しました。
LaravelによるWebシステム開発のご相談はお気軽にどうぞ。