[Laravel11+LIVEWIRE #7]ソース整理します(CRUDが一通りできるようにする)

Laravel
LIVEWIRE LOGO

前回[Laravel11+LIVEWIRE #5]イベント(Events)でイベントの使い方を学んだ。

これまでやってきたところで、問題点となっている一覧の更新がうまくできていないところを修正していく。そしてCSSフレームワーク(Semantic UI)も適用する。

いままでの問題

一覧の表示が再描画(リフレッシュ、再レンダリング)されない原因

コンポーネントの親子関係で親を再描画するときは、親のプロパティを更新しつつ、子のプロパティも更新しないとダメだったっぽい。

今までのソースでコンポーネントの親子関係はこんな感じ。

  • index
    • item(1)
      • edit
      • delete
    • item(2)
      • edit
      • delete
    • create

createコンポーネントでindexコンポーネントを再描画してもitemコンポーネントを再描画していない関係で更新が行われなかったと考えられる。

親子関係の見直し

わかりやすさを優先させるために以下の親子関係にしていく。

  • index
    • create or edit

めちゃシンプルにした。

ソース改修

レイアウト

resources/views/components/layouts/app.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.5.0/dist/semantic.min.css">
        <title>{{ $title ?? 'Page Title' }}</title>
    </head>
    <body>
        <div class="ui container">
            <h2>Livewire学習</h2>
            {{ $slot }}
        </div>
        <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.5.0/dist/semantic.min.js"></script>
    </body>
</html>

Semantic UIを適用し、Semantic UIはjQueryも使うので入れておく。

一覧コンポーネント(indexコンポーネント)

app/Livewire/Post/Index.php

<?php

namespace App\Livewire\Post;

use Livewire\Attributes\On;
use Livewire\Component;
use App\Models\Post;

class Index extends Component
{
    public $posts;
    public $query;
    public $mode;
    public $id;
    public $editPost;

    #[Js]
    public function resetQuery()
    {
        $this->mount();
    }

    #[On('post-created')]
    public function updatePostList()
    {
        $this->posts = Post::whereLike('title', '%' . $this->query . '%')->get();
    }

    public function edit($id)
    {
        $this->mode = 'edit';
        $this->id = $id;
        $this->editPost = Post::findOrFail($id);
        $this->dispatch('edit-post', $this->editPost);

    }

    public function delete($id)
    {
        $post = Post::findOrFail($id);
        $post->delete();
        $this->updatePostList();
    }

    public function mount()
    {
        $this->query = '';
        $this->mode = 'create';
        $this->updatePostList();
    }

    public function render()
    {
        $this->updatePostList();
        return view('livewire.post.index');
    }
}

私が勘違いしていたのはmount()メソッド。初期化メソッドなので、一度しか呼び出されない。何かの操作してmount()メソッドを呼び出すような形で使うのはあまりよろしくないと思われる。あくまで初期化(デフォルト値)をセットするメソッドとしてとらえる。

resources/views/livewire/post/index.blade.php

<div class="ui grid">
    <div class="eight wide column">

        <div class="ui action input">
            <input wire:model.live="query" type="text" placeholder="Search...">
            <button wire:click="resetQuery" class="ui button">reset</button>
        </div>

        <table class="ui celled striped table">
        @foreach ($posts as $post)
            <tr>
                <td>{{ $post->title }}</td>
                <td>{!! nl2br(e($post->message)) !!}</td>
                <td>
                    <button
                        type="button"
                        class="ui red button"
                        wire:click="delete({{ $post->id }})"
                        wire:confirm="Are you sure you want to delete this post?"
                    >
                        削除
                    </button>
                    <button
                        type="button"
                        class="ui secondary button"
                        wire:click="edit({{ $post->id }})"
                    >
                        編集
                    </button>
                </td>
            </tr>
            {{--@livewire(Post\Item::class, ['post' => $post], key($post->id))--}}
        @endforeach
        </table>
    </div>

    <div class="eight wide column">
    @if ($this->mode == 'create')
        @livewire(Post\Create::class)
    @elseif ($this->mode == 'edit')
        @livewire(Post\Edit::class, ['post' => $editPost])
    @endif
    </div>
</div>

検索欄に文字を入力するたびに検索が行われ、一覧の再描画が行われる。

削除ボタンを押すと、削除が行われて、一覧の再描画が行われる。

このコーディングで問題となるのは、検索の部分。wire:model.live="query"となっているから、検索文字を入力するたびにサーバとの通信が行われる。このときindex.phprender()メソッドが呼び出される。updatePostList()メソッドにより一覧データが更新される。
登録処理をまだ記述していないが、登録時にupdatePostList()が呼び出されるわけだが、その後にrender()メソッドが呼び出されるため、$postsプロパティは2回更新されてしまう。DBとの同じ通信が2度行われるのは良くないことなのでもう少し仕組みが必要と思われる。
削除処理についても登録・更新はform内で行われるのに削除はindexコンポーネントで行われるのは良くないと思う…バリデーションも行われていないし。

登録コンポーネント(Createコンポーネント)

app/Livewire/Forms/Post/InputForm.php

<?php

namespace App\Livewire\Forms\Post;

use Illuminate\Validation\Rule;
use Livewire\Attributes\Validate;
use Livewire\Form;

use App\Models\Post;

class InputForm extends Form
{
    public ?Post $post;

    public $title = 'たいとる';

    public $message = '';

    private $kbn = '';

    public function setPost(Post $post)
    {
        $this->post = $post;
        $this->title = $post->title;
        $this->message = $post->message;
    }

    public function rules()
    {
        if ($this->kbn == 'store') {
            $titleUnique = Rule::unique('posts');
        } elseif($this->kbn == 'update') {
            $titleUnique = Rule::unique('posts')->ignore($this->post);
        }
        return [
            'title' => [
                'required',
                'min:3',
                $titleUnique,
            ],
            'message' => 'required|min:3',
        ];
    }

    public function store()
    {

        $this->kbn = 'store';
        $this->validate();

        $post = new Post();
        $post->user_id = 1;
        $post->title = $this->title;
        $post->message = $this->message;
        $post->save();

        $this->reset();

    }

    public function update()
    {
        $this->kbn = 'update';
        $this->validate();

        $this->post->title = $this->title;
        $this->post->message = $this->message;
        $this->post->save();

    }


}

入力フォームに削除メソッドも入れたかったのだけど、まだスキル不足…

app/Livewire/Post/Create.php

<?php

namespace App\Livewire\Post;

use App\Livewire\Forms\Post\InputForm;
use Livewire\Component;

use App\Models\Post;

class Create extends Component
{
    public InputForm $form;

    public function store()
    {
        $this->form->store();
        $this->dispatch('post-created');
    }

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

resources/views/livewire/post/create.blade.php

<div class="ui form @if ($errors->count() > 0) error @endif">
    <h2>登録</h2>
    <form wire:submit="store">
        <div class="field">
            <label for="title">Title:</label>
            <input type="text" id="title" wire:model="form.title" placeholder="タイトル">
        </div>
        @error('form.title')
            <div class="ui error message">{{ $message }}</div>
        @enderror
        <div class="field">
            <label for="message">Message</label>
            <textarea id="message" wire:model="form.message" rows="2"></textarea>
        </div>
        @error('form.message')
            <div class="ui error message">{{ $message }}</div>
        @enderror

        <button class="ui primary button" type="submit">Save</button>
        <span wire:loading>Saving...</span>
    </form>
    {{ session('status') }}
</div>

Saveボタンを押すと、Create.phpstoreメソッドが呼び出され、登録処理後にindexのupdatePostListが呼び出され一覧の再描画が行われる。

更新コンポーネント(Editコンポーネント)

app/Livewire/Post/Edit.php

<?php

namespace App\Livewire\Post;

use App\Livewire\Forms\Post\InputForm;
use Livewire\Attributes\On;
use Livewire\Component;

use App\Models\Post;

class Edit extends Component
{
    public InputForm $form;

    #[On('edit-post')]
    public function edit(Post $post)
    {
        $this->form->setPost($post);
    }


    public function mount(Post $post)
    {
        $this->form->setPost($post);
    }

    public function update()
    {
        $this->form->update();
        $this->dispatch('post-created');
    }

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

resources/views/livewire/post/edit.blade.php

<div class="ui form @if ($errors->count() > 0) error @endif">
    <h2>更新</h2>
    <form wire:submit="update">
        <div class="field">
            <label for="title">Title:</label>
            <input type="text" id="title" wire:model="form.title" placeholder="タイトル">
        </div>
        @error('form.title')
            <div class="ui error message">{{ $message }}</div>
        @enderror

        <div class="field">
            <label for="message">Message</label>
            <textarea id="message" wire:model="form.message" rows="2"></textarea>
        </div>
        @error('form.message')
            <div class="ui error message">{{ $message }}</div>
        @enderror

        <button class="ui primary button" type="submit">更新</button>
        <span wire:loading>Updating...</span>
    </form>
</div>

indexで編集ボタンが押されたら、モードが編集となりedit-postを通じてEditコンポーネントにデータがセットされる。indexの再描画が行われ、登録フォームが非表示となり、更新フォームが表示される。

更新ボタンが押される場合は登録と同じフローとなる。

感想

一通り、マスタメンテナンスができるような処理は記載できたと思う。チームで開発する場合はいろいろとルールを決めないと秩序が乱れる予感がする…

コメント

タイトルとURLをコピーしました