PHP/Laravel

LaravelのLivewireでリアルタイム検索機能を作る方法

こんにちは、シロウです。

皆さんはLaravelのLivewireを使っていますか?

ちなみに僕はあまり使っていません。笑

最近はlaravelとvue.jsで開発しているので、あまり使う機会がありませんでした。

しかし、偶然にも使う機会に恵まれたので、せっかくなので記事にしようかと思います。

そこで、今回の記事ではLaravelのlivewireを使ったリアルタイム検索機能の作成方法をご紹介しようと思います。

下記みたいなものを作っていきます。

それでは早速ですが、みていきましょう。

補足

今回はLaravel のバージョン8.5.22を利用します。

Livewireのインストール

まずはlivewireのパッケージをインストールしていきます。

注意

Laravelの認証機能であるJetstreamをインストールした時にLivewireをインストールしている場合は、この手順は飛ばしてください。

またLivewireをインストールしていない人はターミナルで下記のコマンドを実行してください。

composer require livewire/livewire

これでlivewireがインストールできました。

補足

Livewireがインストールされているかわからないという人はcomopser.json ファイルを確認して"livewire/livewire": "^2.5"こんな感じに書かれていればインストールされています。

livewireを使えるようにapp.blade.phpを書き換える

続いてLivewireを使えるようにするため、app.blade.phpを書き換えていきます。

具体的には下記のように書き換えてください。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    //省略
    @livewireStyles
</head>


<body class="font-sans antialiased">
    //省略
    <main>
        {{ $slot }}
    </main>
    @livewireScripts
</body>

</html>

このようにheadタグ内に@livewireStylesを、bodyタグの最後に@livewireScriptsを追加します。

これでLivewireを使う準備が整いましたので、早速検索機能を作成していきます。

Livewire用のコンポーネントを作成する

続いて下記のコマンドを実行してください。

php artisan make:livewire search

これで「app/Http/Livewire/Search.php」と「resources/views/livewire/search.blade.php」が作成されたはずです。

web.phpにルーティングを追加する

今作成したsearch.blade.phpを表示するためのルーティングを追加します。

そのため、web.phpに下記の2行を追加してください。

use App\Http\Livewire\Search;

Route::get('/search', Search::class)->name('search');

こちらはLivewireをフルページコンポーネントとして利用するときに使える記述方法です。

例えば、Livewireを「いいね機能で使うハートマークの部分」や「フォローボタン」などで使う場合は、従来のLaravelのbladeにlivewireを取り込むような形になります。

しかし、今回の場合は検索機能ページを丸々Livewireで作成します。

そのような場合は、今回のように「コンポーネントを継承したクラス(今回の場合はSearchクラス)」を返します。

今回は/searchにアクセスしたら、App\Http\Livewire\Search.phpに返すように記述しています。

そのため、続いてApp\Http\Livewire\Search.phpを編集していきます。

公式ドキュメントでは「Full-Page Components」の部分が該当します。

補足

難しく考えすぎず、丸々一ページを非同期に対応させたい場合は、今回のような書き方をするよと考えてください!

ちなみにLivewireを部分的に使いたい場合は「<livewire:search/>」のように書くことで実現できます。(今回はこっちの使い方はしませんm(_ _)m)

Search.phpを編集する

app/Http/Livewire/Search.phpを編集していきます。

一旦、今回のコードを全て載せておくので、コピペで貼り付けてしまってください。(モデルや検索に一致させたいカラム名などは各自でカスタマイズしてください。m(_ _)m)

//app/Http/Livewire/Search.php
<?php

namespace App\Http\Livewire;

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

class Search extends Component
{

    public $word = '';
    public $type = '';
    protected $queryString = [
        'word' => ['except' => ''],
        'type' => ['except' => '']
    ];
    
    protected $posts;

    public function updatedType()
    {
        $this->search();
    }

    public function render()
    {
        $this->search();

        return view('livewire.search', ['posts'=>$this->posts])->layout('welcome');
    }

    public function search()
    {
        $word = $this->word;
        if ($this->type === 'new' || $this->type ==='') {
            $this->posts = Post::where('title', 'like', '%'.$this->word.'%')->orderBy('created_at', 'desc')->get();
        } elseif ($this->type === 'old') {
            $this->posts = Post::where('title', 'like', '%'.$this->word.'%')->orderBy('created_at', 'asc')->get();
        }
    }
}

 

よくわからないコードが並んでいると思いますが、おいおい解説していきますので、今はまだわからなくて大丈夫です。

現在では下記のコードさえ理解していれば大丈夫です。($this->searchは一旦スルーしておいてください。笑)

    public function render()
    {
        $this->search();

        return view('livewire.search', ['posts'=>$this->posts])->layout('welcome');
    }

Livewireを使うときはこのようにrenderメソッドの中でreturn view使ってviewを返してあげることで、viewを表示します。

今回の場合はlivewire.searchを第一引数に指定しているのでresources/views/livewire/search.blade.phpが表示されます。

viewメソッドは第二引数に変数を与える事ができるので、今回はpostsを返しています。(postsは後で使います。)

layout('welcome')としているのはLivewireをフルページコンポーネンとして利用する場合に「どのページを引き継ぐか」を指定するメソッドになります。

このようにすることでwelcome.blade.php{{ $slot }}の部分にこれから記述する内容が表示されるようになります。

ここで一旦、URLに/searchと入力して「Search.blade.php」の内容が表示されれば成功です。

補足

layout('welcome')の部分は自身で好きなものに変更してください。

おそらくjetstreamなどを利用している人はlayout('layouts.app')になるかと思います。

search.blade.phpを編集していく

続いて、検索ページを表示します。

resources/views/livewire/search.blade.phpを下記のように編集してください。

<div>
    <form wire:submit.prevent="render" method="GET">
        <div>
            <input type="text" wire:model.defer="word">
            <button>
                検索
            </button>
        </div>

        <select wire:model.lazy="type">
            <option value="new">新着</option>
            <option value="old">投稿が古い順</option>
        </select>
    </form>

    <div>
        @foreach ($posts as $post)
        <p>{{$posts->title}}</p>
        @endforeach
        @if ($posts->count() === 0)
        検索キーワードに一致する検索結果がありません。
        @endif
    </div>
</div>

ひとまずはこれで冒頭で紹介した検索機能が完成しました。

コードの解説

それでは順にここまで作ったコードを解説していきます。

formについて

まずは下記のコードについて解説します。

    <form wire:submit.prevent="render" method="GET">
        <div>
            <input type="text" wire:model.defer="word">
            <button>
                検索
            </button>
        </div>

        <select wire:model.lazy="type">
            <option value="new">新着</option>
            <option value="old">投稿が古い順</option>
        </select>
    </form>

wire:model.defer="word"wire:model.lazy="type"などがありますが、このように記述することで変数をリアルタイムに更新(Data Binding:データバインディング)する事ができます。

どういうことかというと、wordtypeSearch.phpで宣言している変数です。(実際のコードは下記のようになっています。)

//app/Http/Livewire/Search.php
    public $word = '';
    public $type = '';

このようにpublicで変数を宣言することで、bladeファイルでも変数を利用できるようになります。

このwordやtypeをwire:model="変数名"のようにすることで、変数の値が「テキストフィールド」や「セレクトボックス」の値と同期(バイディング)するのです。

補足
wire:model="変数"は下記のタグでのみ使用する事ができます。

<input type=”text”>
<input type=”radio”>
<input type=”checkbox”>
<select>
<textarea>

では、deferlazyは何かというと、これらは同期(バインディング)するタイミングを制御するものになります。

ただ、この辺りを考え出すと、いきなりは辛いものがあると思うので、余裕がある方は調べてみてください。(余裕がない人は今は「ふーん」って感じで良いと思います。)

 

続いてwire:submit.prevent="render"としていますが、これは「formを送信したらSearch.phprenderメソッドを呼び出す」という処理です。

このrenderの部分をaとすれば、aメソッドを呼び出しますし、hogeならhogeメソッドを呼び出しますので、自由に変更可能です。

では、このrenderメソッドがどのようになっているのか見ていきます。

Search.phpのrenderメソッドについて

Search.phpのrenderメソッドは下記のようになっていました。

    public function render()
    {
        $this->search();

        return view('livewire.search', ['posts'=>$this->posts])->layout('welcome');
    }

renderメソッドは前述したように「最初の読み込み時と、それ以降の更新時全てで呼び出されるメソッド」です。

そのため、ここで検索を実行するsearchメソッドを呼んであげれば良いでしょう。

それでは冒頭でスルーした$this->search()を見ていきます。

searchメソッドは下記のようになっていました。

    public function search()
    {
        $word = $this->word;
        if ($this->type === 'new' || $this->type ==='') {
            $this->posts = Post::where('title', 'like', '%'.$this->word.'%')->orderBy('created_at', 'desc')->paginate(6);
        } elseif ($this->type === 'old') {
            $this->posts = Post::where('title', 'like', '%'.$this->word.'%')->orderBy('created_at', 'asc')->paginate(6);
        }
    }

まず最初に$word = $this->word;で「現在の検索ボックス(inputフィールド)に入力されている値」を$wordに代入しています。

前述したように、変数word<input type="text" wire:model.defer="word">によって、inputフィールドと同期(バインディング)しています。

そのため、$this->wordとすることで『まさに今、検索フォームに入力されている値』を取得する事ができます。

同様にif文内で$this->typeとすることで『セレクトボックスで選択されている値』によって条件分岐させる事ができます。

あとはLike検索をして、条件に一致したものをpostsに代入しているだけですね。

search.blade.phpにはreturn viewで今取得した$postsを渡してあげています。

そのため、検索した結果がリアルタイムで変更されるというようになっています。

$queryStringについて

$queryStringに変数を指定してあげると、指定した変数がURLに反映されるようになります。

例えば、今回の場合は下記のようにしています。

protected $queryString = [
'word' => ['except' => ''],
'type' => ['except' => '']
];

そのため、例えば、検索ボックスに「テスト」と入力したらURLに/?search=テストのように表示されるようになります。

そしてexceptにはURLに表示したくない時の値を指定します。

そのため、今回のようにしていると「wordtype`が空文字の時はURLに表示されない。』という挙動が可能になります。

updatedTypeについて

updatedTypeについても解説しておきます。

Livewireにはライフサイクルフックという「特定のタイミングで呼ばれるメソッド」が存在します。

今回のupdatedの場合は「データが更新されたタイミング」で発火するメソッドです。

    public function updatedType()
    {
        $this->search();
    }

このようにしてあげることで$typeが変更されたタイミング』でこのメソッドが呼ばれます。

今回はセレクトボックスが変更されたタイミングで検索を実行したかったので、このようにしてみました。

ちなみに他のライフサイクルメソッドには、下記のようなものがあります。

$postsがpublicではなくprotectedなのは何故か

最後に$postsprotectedで記述しているのは何故かということについてです。

実は、今回の場合はpublicとしても問題ありません。

ただ、ページネーションを使おうとするとpublicのままにしているとエラーが発生してしまします。

しかし、今回のようにprotectedにしてviewメソッドから値を返してあげるようにすればエラーが発生しません。

おそらく多くの人はページネーションを利用すると思うので、今回のようにしておきました。

 

一通りの解説は終わったので今回はこれにて終了です。お疲れ様でした!