PCS開発チーム

PCS開発チーム

Livewireの普通の使い方とVoltの比較

バージョン

最近でも機能が追加されてるので2023年12月時点の情報。

ドキュメント

https://livewire.laravel.com/docs/volt

3つのスタイル

一つのLaravelプロジェクト内で3つのスタイルを同時に使える。簡単なページはVolt functional、複雑なページは普通などで使い分けしてもいい。最初はVolt functionalで作ったけど複雑になってきたので普通の使い方に作り変えるなんてこともよく発生する。

Livewire スタンダード

PHPファイルとBladeファイルが分かれた普通の使い方。
実装例:JetstreamのLivewireスタック。

Voltの前に普通の使い方の理解は必須。
この記事は普通の使い方でのこの機能はVoltでどう書くのかって情報のまとめ。

Volt functional

BladeファイルだけでLivewireを使う新しい方法。Vue.js風の表現ならシングルファイルLivewireコンポーネント。
実装例:Breezeのlivewire-functionalスタック。 https://github.com/laravel/breeze/tree/1.x/stubs/livewire-functional/resources/views/livewire

<?php
 
use function Livewire\Volt\{state};
 
state(['count' => 0]);
 
$increment = fn () => $this->count++;
 
?>
 
<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>

Laravel Folioと似てるし一緒に発表されたけどFolioとは関係ない。

Volt Class-based

Voltを普通の使い方に近い無名classを使うスタイルにしたもの。
実装例:Breezeのlivewireスタック。 https://github.com/laravel/breeze/tree/1.x/stubs/livewire/resources/views/livewire

<?php
 
use Livewire\Volt\Component;
 
new class extends Component {
    public $count = 0;
 
    public function increment()
    {
        $this->count++;
    }
} ?>
 
<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>

複雑化しただけでこれの追加は余計だった気がする。Laravelは複数の使い方ができるのが混乱の元なのに余計な選択肢を増やすのは明らかに悪い。

コンポーネント作成コマンド

Livewire スタンダード

php artisan make:livewire counter

Volt functional

php artisan make:volt counter

ファイル一つなので2回目からはコマンドを使うより既存のコンポーネントを複製して書き換えが現実的な使い方。

Volt Class-based

functionalと同じ。

ファイルの場所

Livewire スタンダード

  • app/Livewire/にPHPファイル
  • resources/views/livewireにBladeファイル

Volt functional

resources/views/livewireにBladeファイルのみ。

resources/views/pagesに置いたBladeファイルも使える。Folio用に作ったページだけど機能を追加したくなってVoltに変更、みたいな使い方を想定しているのだろう。FolioのファイルベースルーティングとVoltを同時に使えるかは試してないので不明。

Volt Class-based

functionalと同じ。

プロパティとmount

Livewire スタンダード

<?php
 
namespace App\Livewire;

use App\Models\Post;
use Livewire\Component;
 
class PostIndex extends Component
{
    public $posts;
    
    public function mount()
    {
        $this->posts = Post::all(); 
    }
}

Volt functional

<?php
use App\Models\Post;
use function Livewire\Volt\state;
use function Livewire\Volt\mount;

state('posts');

mount(function () {
    $this->posts = Post::all(); 
});
?>
<div></div>

Volt Class-based

<?php

use App\Models\Post;
use Livewire\Volt\Component;
 
new class extends Component {
    public $posts;
 
    public function mount()
    {
        $this->posts = Post::all(); 
    }
}
?>
<div></div>

アクション

Livewire スタンダード

namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class CreatePost extends Component
{
    public $title = '';
 
    public $content = '';
 
    public function save()
    {
        Post::create([
            'title' => $this->title,
            'content' => $this->content,
        ]);
 
        return redirect()->to('/posts');
    }
}
<form wire:submit="save"> 
    <input type="text" wire:model="title">
 
    <textarea wire:model="content"></textarea>
 
    <button type="submit">Save</button>
</form>

Volt functional

<?php

use App\Models\Post;
use function Livewire\Volt\state;
 
state(['title', 'content']);
 
$save = function () {
    Post::create([
        'title' => $this->title,
        'content' => $this->content,
    ]);
 
    return redirect()->to('/posts');
};
?>

<div>
    <form wire:submit="save"> 
        <input type="text" wire:model="title">
 
        <textarea wire:model="content"></textarea>
 
        <button type="submit">Save</button>
    </form>
</div>

Volt Class-based

スタンダードとほぼ同じなので以降は省略。

バリデーション

Livewire スタンダード

Validateアトリビュートで指定。

use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\Post;
 
class CreatePost extends Component
{
    #[Validate('required|min:3')] 
    public $title = '';
 
    #[Validate('required|min:3')] 
    public $content = '';
}

Volt functional

rules()で指定。

<?php
 
use function Livewire\Volt\{rules};
 
rules(['name' => 'required|min:6', 'email' => 'required|email']);
 
$submit = function () {
    $this->validate();
 
    // ...
};
 
?>
 
<form wire:submit.prevent="submit">
    //
</form>

Computed プロパティ

Livewire スタンダード

use Livewire\Attributes\Computed;
use Livewire\Component;
use App\Models\User;
 
class ShowUser extends Component
{ 
    #[Computed]
    public function count()
    {
        return User::count();
    }

Volt functional

<?php
 
use App\Models\User;
use function Livewire\Volt\{computed};
 
$count = computed(function () {
    return User::count();
});
 
?>
 
<div>
    {{ $this->count }}
</div>

ページネーション

Livewire スタンダード

WithPaginationの追加を忘れやすい。

<?php
 
namespace App\Livewire;
 
use Livewire\WithPagination;
use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
    use WithPagination;
 
    public function render()
    {
        return view('show-posts', [
            'posts' => Post::paginate(10),
        ]);
    }
}

Volt functional

<?php
 
use function Livewire\Volt\{with, usesPagination};
 
usesPagination();
 
with(fn () => ['posts' => Post::paginate(10)]);
 
?>
 
<div>
    @foreach ($posts as $post)
        //
    @endforeach
 
    {{ $posts->links() }}
</div>

ファイルアップロード

Livewire スタンダード

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
 
class UploadPhoto extends Component
{
    use WithFileUploads;
 
    #[Validate('image|max:1024')]
    public $photo;
 
    public function save()
    {
        $this->photo->store('photos');
    }
}

Volt functional

use function Livewire\Volt\{state, usesFileUploads};
 
usesFileUploads();
 
state(['photo']);
 
$save = function () {
    $this->validate([
        'photo' => 'image|max:1024',
    ]);
 
    $this->photo->store('photos');
};

Full-page component レイアウト指定

Livewire スタンダード

render()かclassにLayoutアトリビュートで指定。レイアウトは基本的に固定なのでアトリビュートでの指定でいいだろう。

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
class CreatePost extends Component
{
    // ...
 
    #[Layout('layouts.app')] 
    public function render()
    {
        return view('livewire.create-post');
    }
}
<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
#[Layout('layouts.app')] 
class CreatePost extends Component
{
    // ...
}

Volt functional

layout()で指定。

use function Livewire\Volt\{layout, state};
 
state('users');
 
layout('components.layouts.admin');

Full-page component title指定

titleだけ特別な扱い。

先にレイアウトファイルに$titleを用意。

<title>{{ $title ?? config('app.name') }}</title>

Livewire スタンダード

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Layout;
use Livewire\Component;
 
class Post extends Component
{
    public $post;
 
    public function render()
    {
        return view('livewire.post-show')
            ->title($this->post->title); 
    }
}

ちなみにLivewire3のドキュメントからは消えてるけどtitle以外のデータをレイアウトに渡すlayoutData()もある。

    public function render()
    {
        return view('livewire.post-show')
            ->layoutData(['foo' => 'bar']); 
    }

Titleアトリビュートで指定する方法もあるけど固定のtitleでしか使えないので使うことはほとんどない。

use Livewire\Attributes\Title;
use Livewire\Component;
 
class CreatePost extends Component
{
    // ...
 
    #[Title('Create Post')] 
    public function render()
    {
        return view('livewire.create-post');
    }
}

Volt functional

use function Livewire\Volt\{state, title};
 
state('post');
 
title(fn() => $this->post->title);

固定のtitleならtitie('Post');も可能だけどこれもほとんど使わないだろう。

Volt Class-based

ここだけ特殊でrendering()で指定。

<?php
 
use Illuminate\View\View;
use Livewire\Volt\Component;
 
new class extends Component {
    public function rendering(View $view): void
    {
        $view->title('Create Post');
 
        // ...
    }
 
    // ...

Full-page component ルーティング

Livewire スタンダード

PHPファイルを指定。

use App\Livewire\CreatePost;
 
Route::get('/posts/create', CreatePost::class)->name('post.create');

Volt functional

Bladeファイルを指定。Volt::route()の戻り値はRouteなので通常通りnameの指定なども可能。

use Livewire\Volt\Volt;
 
Volt::route('/users', 'user-index')->name('user.index');

URLクエリパラメータ

Livewire スタンダード

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Url;
use Livewire\Component;
 
class ShowUsers extends Component
{
    #[Url] 
    public $search = '';

Volt functional

<?php
 
use function Livewire\Volt\{state};
 
state(['search'])->url();

イベントリスナー

Livewire スタンダード

use Livewire\Component;
use Livewire\Attributes\On; 
 
class Dashboard extends Component
{
    #[On('post-created')] 
    public function updatePostList($title)
    {
        // ...
    }
}

Volt functional

use function Livewire\Volt\{on};
 
on(['post-created' => function () {
    //
}]);

以上

よく使うだろう機能はまとめたのでこれ以上はドキュメントを参照。

アドベントカレンダー用の記事。
https://qiita.com/advent-calendar/2023/laravel

ここは長文を想定してないけど最近は外部に書いてないのでここに書く。
ドメインを破棄した時用にGitHubにも残している。
https://github.com/pop-culture-studio/articles/blob/main/livewire-volt.md