jpskill.com
🛠️ 開発・MCP コミュニティ

laravel-livewire

Laravel Livewire v4以降で、PHPのみを使いJavaScriptなしで動的なWebインターフェースを開発するSkill。

📜 元の英語説明(参考)

Full-stack Laravel framework for building dynamic, reactive interfaces using PHP without writing JavaScript. Use when creating or modifying Livewire components, implementing data binding with wire:model, working with lifecycle hooks, building forms with validation, handling events and parent-child communication, implementing file uploads/pagination/lazy loading, writing tests, or optimizing performance. Supports Laravel Livewire v4+ development. Keywords: Livewire, wire:model, wire:click, livewire component, Alpine.js integration, wire:submit, real-time validation, computed properties, Laravel Livewire.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Laravel Livewire v4以降で、PHPのみを使いJavaScriptなしで動的なWebインターフェースを開発するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-17
取得日時
2026-05-17
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Laravel Livewire

概要

PHPのみを使用して、動的でリアクティブなLaravelインターフェースを構築します。Livewire v4+は、ハイドレーション/デハイドレーションによる自動クライアントサイド更新でサーバーサイドレンダリングを処理します。JavaScriptは不要です。

クイックスタート

インストール

composer require livewire/livewire
php artisan livewire:layout  # resources/views/layouts/app.blade.php を作成します

コンポーネントの作成

# シングルファイルコンポーネント (デフォルト)
php artisan make:livewire CreatePost

# ページコンポーネント
php artisan make:livewire pages::post.create

# マルチファイルコンポーネント
php artisan make:livewire CreatePost --mfc

# クラスベースコンポーネント (従来型)
php artisan make:livewire CreatePost --class

基本的なコンポーネントパターン

<?php
use Livewire\Component;
new class extends Component {
    public string $title = '';
    public string $content = '';

    public function save()
    {
        $this->validate([
            'title' => 'required|max:255',
            'content' => 'required',
        ]);

        Post::create($this->only(['title', 'content']));
        return $this->redirect('/posts');
    }
};
?>
<form wire:submit="save">
    <input type="text" wire:model="title">
    @error('title') <span class="error">{{ $message }}</span> @enderror

    <textarea wire:model="content"></textarea>
    @error('content') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">Save</button>
</form>

コアコンセプト

コンポーネントの構造

シングルファイルコンポーネント (resources/views/components/post/⚡create.blade.php):

  • PHPクラスとBladeテンプレートが1つのファイルに
  • 稲妻マーク (⚡) はオプションで、設定で無効にできます

マルチファイルコンポーネント (resources/views/components/post/⚡create/):

  • PHP、Blade、JS、CSSが個別のファイルに
  • 大規模なJavaScriptを伴う大きなコンポーネントに適しています

クラスベースコンポーネント (app/Livewire/CreatePost.php):

  • 従来のLaravel構造
  • Livewire v2/v3開発者には馴染み深い

プロパティ管理

// Public properties - テンプレートで $property としてアクセス可能
public $title = '';

// Protected properties - $this->property としてアクセス可能、クライアントには送信されない
protected $apiKey = 'secret';

// 型付きプロパティ
public string $email = '';
public int $count = 0;
public ?Post $post;  // IDを自動ロック

// プロパティのリセット
$this->reset('title', 'content');
$value = $this->pull('title');  // 取得してリセット

ライフサイクルフック

フック 実行タイミング
mount() 初回ロード時のみ - props/ルートパラメータを受け取る
boot() すべてのリクエスト (初回 + 以降)
hydrate() 以降のリクエストの開始時
dehydrate() すべてのリクエストの終了時
updating($prop) プロパティ更新前
updated($prop) プロパティ更新後
rendering() render() の前
rendered() render() の後
exception($e) 例外がスローされた時
public function mount(Post $post)
{
    $this->post = $post;
    $this->title = $post->title;
}

public function updatedTitle($value)
{
    $this->title = strtolower($value);
}

算出プロパティ

メモ化された派生値 — $this->property を介してアクセスします。

use Livewire\Attributes\Computed;

#[Computed]
public function posts()
{
    return Post::all(); // リクエストごとに1回実行
}

#[Computed(persist: true, seconds: 3600)]
public function cachedData()
{
    return ExpensiveModel::all();
}

Bladeでの使用法: @foreach ($this->posts as $post)

データバインディング

wire:model修飾子

修飾子 動作
(デフォルト) アクション送信時のみ更新
.live ユーザーが入力するたびに更新 (150msのデバウンス)
.blur ユーザーがクリックして離れたときに更新
.change 選択時に即座に更新
.debounce.500ms カスタムデバウンス期間
.number サーバーでintにキャスト
.boolean サーバーでboolにキャスト
<input type="text" wire:model="title">
<input type="email" wire:model.live="email">  <!-- ライブバリデーション -->
<input type="text" wire:model.blur="title">   <!-- フォーカスが外れたとき -->
<input type="text" wire:model.live.debounce.500ms="search">

依存するセレクト (重要!)

あるセレクトが別のセレクトに依存する場合、wire:key を使用します。

<select wire:model.live="selectedState">
    @foreach(State::all() as $state)
        <option value="{{ $state->id }}">{{ $state->label }}</option>
    @endforeach
</select>

<select wire:model.live="selectedCity" wire:key="{{ $selectedState }}">
    @foreach(City::whereStateId($selectedState)->get() as $city)
        <option value="{{ $city->id }}">{{ $city->label }}</option>
    @endforeach
</select>

アクションとイベント

イベントリスナー

<button wire:click="save">Save</button>
<input wire:keydown.enter="search">
<form wire:submit="submitForm">
<button wire:click="delete({{ $post->id }})">Delete</button>

イベント修飾子

<!-- キー修飾子 -->
<input wire:keydown.enter="search">
<input wire:keydown.shift.enter="...">

<!-- イベント修飾子 -->
<button wire:click.prevent="save">
<button wire:click.stop="...">
<button wire:click.window="...">
<button wire:click.once="...">
<button wire:click.debounce.250ms="...">

イベントのディスパッチ

PHPから:

$this->dispatch('post-created', postId: $post->id);
$this->dispatch('post-created')->to(Dashboard::class);  // コンポーネントに直接

Bladeから (クライアントサイド):

<button wire:click="$dispatch('post-created', { id: {{ $post->id }} })">

PHPでのリスニング:

use Livewire\Attributes\On;

#[On('post-created')]
public function handlePostCreated($postId)
{
    // イベントを処理
}

Bladeでのリスニング:

<livewire:post-list @post-created="$refresh" />

親子コンポーネント間の通信

<!-- propsの受け渡し -->
<livewire:todo-item :$post />

<!-- リアクティブなprops (親が変更されると子が更新される) -->
<?php
use Livewire\Attributes\Reactive;
#[Reactive]
public $todos;
?>

<!-- 親への直接アクセス -->
<butto
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Laravel Livewire

Overview

Build dynamic, reactive Laravel interfaces using only PHP. Livewire v4+ handles server-side rendering with automatic client-side updates via hydration/dehydration—no JavaScript required.

Quick Start

Installation

composer require livewire/livewire
php artisan livewire:layout  # Creates resources/views/layouts/app.blade.php

Create a Component

# Single-file component (default)
php artisan make:livewire CreatePost

# Page component
php artisan make:livewire pages::post.create

# Multi-file component
php artisan make:livewire CreatePost --mfc

# Class-based component (traditional)
php artisan make:livewire CreatePost --class

Basic Component Pattern

<?php
use Livewire\Component;
new class extends Component {
    public string $title = '';
    public string $content = '';

    public function save()
    {
        $this->validate([
            'title' => 'required|max:255',
            'content' => 'required',
        ]);

        Post::create($this->only(['title', 'content']));
        return $this->redirect('/posts');
    }
};
?>
<form wire:submit="save">
    <input type="text" wire:model="title">
    @error('title') <span class="error">{{ $message }}</span> @enderror

    <textarea wire:model="content"></textarea>
    @error('content') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">Save</button>
</form>

Core Concepts

Component Structure

Single-file components (resources/views/components/post/⚡create.blade.php):

  • PHP class and Blade template in one file
  • Lightning bolt (⚡) is optional and can be disabled in config

Multi-file components (resources/views/components/post/⚡create/):

  • Separate files for PHP, Blade, JS, CSS
  • Better for large components with significant JavaScript

Class-based components (app/Livewire/CreatePost.php):

  • Traditional Laravel structure
  • Familiar for Livewire v2/v3 developers

Property Management

// Public properties - accessible in template as $property
public $title = '';

// Protected properties - accessible as $this->property, not sent to client
protected $apiKey = 'secret';

// Typed properties
public string $email = '';
public int $count = 0;
public ?Post $post;  // Auto-locks ID

// Reset properties
$this->reset('title', 'content');
$value = $this->pull('title');  // Get and reset

Lifecycle Hooks

Hook When It Runs
mount() First load only - receive props/route params
boot() Every request (initial + subsequent)
hydrate() Beginning of subsequent requests
dehydrate() End of every request
updating($prop) Before property update
updated($prop) After property update
rendering() Before render()
rendered() After render()
exception($e) When exception thrown
public function mount(Post $post)
{
    $this->post = $post;
    $this->title = $post->title;
}

public function updatedTitle($value)
{
    $this->title = strtolower($value);
}

Computed Properties

Memoized derived values—accessed via $this->property.

use Livewire\Attributes\Computed;

#[Computed]
public function posts()
{
    return Post::all(); // Runs once per request
}

#[Computed(persist: true, seconds: 3600)]
public function cachedData()
{
    return ExpensiveModel::all();
}

Usage in blade: @foreach ($this->posts as $post)

Data Binding

wire:model Modifiers

Modifier Behavior
(default) Updates only on action submit
.live Updates as user types (150ms debounce)
.blur Updates when user clicks away
.change Updates immediately on selection
.debounce.500ms Custom debounce duration
.number Cast to int on server
.boolean Cast to bool on server
<input type="text" wire:model="title">
<input type="email" wire:model.live="email">  <!-- Live validation -->
<input type="text" wire:model.blur="title">   <!-- On blur -->
<input type="text" wire:model.live.debounce.500ms="search">

Dependent Selects (Important!)

Use wire:key when one select depends on another.

<select wire:model.live="selectedState">
    @foreach(State::all() as $state)
        <option value="{{ $state->id }}">{{ $state->label }}</option>
    @endforeach
</select>

<select wire:model.live="selectedCity" wire:key="{{ $selectedState }}">
    @foreach(City::whereStateId($selectedState)->get() as $city)
        <option value="{{ $city->id }}">{{ $city->label }}</option>
    @endforeach
</select>

Actions & Events

Event Listeners

<button wire:click="save">Save</button>
<input wire:keydown.enter="search">
<form wire:submit="submitForm">
<button wire:click="delete({{ $post->id }})">Delete</button>

Event Modifiers

<!-- Key modifiers -->
<input wire:keydown.enter="search">
<input wire:keydown.shift.enter="...">

<!-- Event modifiers -->
<button wire:click.prevent="save">
<button wire:click.stop="...">
<button wire:click.window="...">
<button wire:click.once="...">
<button wire:click.debounce.250ms="...">

Dispatching Events

From PHP:

$this->dispatch('post-created', postId: $post->id);
$this->dispatch('post-created')->to(Dashboard::class);  // Direct to component

From Blade (client-side):

<button wire:click="$dispatch('post-created', { id: {{ $post->id }} })">

Listening in PHP:

use Livewire\Attributes\On;

#[On('post-created')]
public function handlePostCreated($postId)
{
    // Handle event
}

Listening in Blade:

<livewire:post-list @post-created="$refresh" />

Parent-Child Communication

<!-- Passing props -->
<livewire:todo-item :$post />

<!-- Reactive props (child updates when parent changes) -->
<?php
use Livewire\Attributes\Reactive;
#[Reactive]
public $todos;
?>

<!-- Direct parent access -->
<button wire:click="$parent.remove({{ $id }})">Remove</button>

Forms & Validation

Validation with Attributes

use Livewire\Attributes\Validate;

new class extends Component {
    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|email', message: 'Please enter a valid email')]
    public $email = '';

    public function save()
    {
        $this->validate(); // Runs all rules
        Post::create($this->only(['title', 'email']));
    }
};

Real-Time Validation

<input type="text" wire:model.live="title">
@error('title') <span class="error">{{ $message }}</span> @enderror
public function updated($property)
{
    $this->validateOnly($property);
}

Form Objects

Extract form logic into reusable classes:

php artisan livewire:form PostForm
// app/Livewire/Forms/PostForm.php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|min:5')]
    public $content = '';

    public function store()
    {
        $this->validate();
        Post::create($this->only(['title', 'content']));
    }
}
<input type="text" wire:model="form.title">
@error('form.title') <span class="error">{{ $message }}</span> @enderror

Loading States

<button wire:click="save">
    Save
    <span wire:loading>Saving...</span>
</button>

<!-- Target specific action -->
<div wire:loading wire:target="removePhoto">Removing...</div>

<!-- CSS attribute -->
<button class="data-loading:opacity-50">Save</button>

Advanced Features

Lazy Loading

<livewire:revenue-chart lazy />
use Livewire\Attributes\Lazy;

#[Lazy]
class RevenueChart extends Component
{
    public function placeholder()
    {
        return view('livewire.placeholders.skeleton');
    }
}

Polling

<div wire:poll>{{ $count }}</div>           <!-- Every 2.5s -->
<div wire:poll.15s>{{ $count }}</div>       <!-- Custom interval -->
<div wire:poll.visible>{{ $count }}</div>   <!-- Only when visible -->
<div wire:poll.keep-alive>{{ $count }}</div> <!-- Keep in background -->

File Uploads

use Livewire\WithFileUploads;

class UploadPhoto extends Component
{
    use WithFileUploads;

    #[Validate('image|max:1024')] // 1MB max
    public $photo;

    public function save()
    {
        $this->photo->store(path: 'photos');
    }
}
<form wire:submit="save">
    @if ($photo)
        <img src="{{ $photo->temporaryUrl() }}">
    @endif
    <input type="file" wire:model="photo">
</form>

Pagination

use Livewire\WithPagination;

class ShowPosts extends Component
{
    use WithPagination;

    public function render()
    {
        return view('livewire.show-posts', [
            'posts' => Post::paginate(10),
        ]);
    }
}
{{ $posts->links() }}

Alpine.js Integration

<div x-data="{ expanded: false }">
    <button @click="expanded = !expanded">Toggle</button>
    <div x-show="expanded">
        {{ $content }}
    </div>
</div>

Access Livewire from Alpine:

<input x-on:blur="$wire.save()">
<span x-text="$wire.title.length"></span>

Testing

use Livewire\Livewire;

test('component renders', function () {
    Livewire::test(CreatePost::class)
        ->assertStatus(200);
});

test('can create post', function () {
    Livewire::test(CreatePost::class)
        ->set('title', 'Test Post')
        ->call('save')
        ->assertRedirect('/posts');
});

test('validation works', function () {
    Livewire::test(CreatePost::class)
        ->set('title', '')
        ->call('save')
        ->assertHasErrors('title');
});

Routing

// routes/web.php
Route::livewire('/posts/create', 'pages::post.create');
Route::livewire('/posts/{id}', 'pages::post.show');
Route::livewire('/posts/{post}', 'pages::post.edit'); // Model binding

PHP Attributes Reference

Attribute Purpose
#[Validate('rule')] Add validation rules to properties
#[Computed] Create memoized derived properties
#[Computed(persist: true)] Cache computed across requests
#[Locked] Prevent client-side modification
#[Reactive] Props update when parent changes
#[On('event')] Listen for dispatched events
#[Lazy] Defer component loading
#[Session] Persist properties in session
#[Url] Sync with query string
#[Renderless] Skip re-render after action
#[Async] Execute action in parallel
#[Layout('name')] Specify custom layout
#[Title('Title')] Set page title
#[Js] Return JSON for JavaScript consumption

Common Gotchas

  1. Computed properties require $this — use $this->posts, not $posts
  2. Default wire:model doesn't update as you type — use .live modifier
  3. Dependent selects need wire:key — prevents stale options
  4. Props aren't reactive by default — use #[Reactive] attribute
  5. Always validate/authorize properties — treat as user input
  6. Use only() or except() to limit data sent to client

Security Best Practices

  1. Always authorize action parameters — users can call any public method
  2. Use #[Locked] for sensitive IDs to prevent manipulation
  3. Mark dangerous methods as protected/private — prevents client access
  4. Validate all input — use #[Validate] or rules() method
  5. Never trust client-side data — properties are user input

Performance Tips

  1. Use computed properties for expensive queries
  2. Lazy load components below the fold
  3. Use .blur instead of .live when real-time isn't needed
  4. Avoid storing large Eloquent collections as properties
  5. Use wire:key for list items to prevent DOM thrashing
  6. Debounce live updates for better performance
  7. Cache expensive operations with #[Computed(persist: true)]

Resources

For Component Architecture

See references/core.md — components, properties, lifecycle, actions

For Forms & Validation

See references/forms.md — form handling, validation, file uploads

For Advanced Features

See references/advanced.md — nesting, events, computed properties, pagination

For Directives

See references/directives.md — all wire:* directives

For Attributes

See references/attributes.md — all PHP attributes

For Integration

See references/integration.md — Alpine.js, JavaScript, security

For Testing

See references/testing.md — Pest/PHPUnit patterns