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:データバインディング)する事ができます。
どういうことかというと、word
やtype
はSearch.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>
では、defer
やlazy
は何かというと、これらは同期(バインディング)するタイミングを制御するものになります。
ただ、この辺りを考え出すと、いきなりは辛いものがあると思うので、余裕がある方は調べてみてください。(余裕がない人は今は「ふーん」って感じで良いと思います。)
続いてwire:submit.prevent="render"
としていますが、これは「formを送信したらSearch.php
のrenderメソッド
を呼び出す」という処理です。
この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に表示したくない時の値を指定します。
そのため、今回のようにしていると「word
やtype`
が空文字の時はURLに表示されない。』という挙動が可能になります。
updatedTypeについて
updatedType
についても解説しておきます。
Livewireにはライフサイクルフックという「特定のタイミングで呼ばれるメソッド」が存在します。
今回のupdated
の場合は「データが更新されたタイミング」で発火するメソッドです。
public function updatedType()
{
$this->search();
}
このようにしてあげることで『$type
が変更されたタイミング』でこのメソッドが呼ばれます。
今回はセレクトボックスが変更されたタイミングで検索を実行したかったので、このようにしてみました。
ちなみに他のライフサイクルメソッドには、下記のようなものがあります。
$postsがpublicではなくprotectedなのは何故か
最後に$posts
をprotected
で記述しているのは何故かということについてです。
実は、今回の場合はpublic
としても問題ありません。
ただ、ページネーションを使おうとするとpublic
のままにしているとエラーが発生してしまします。
しかし、今回のようにprotected
にしてviewメソッド
から値を返してあげるようにすればエラーが発生しません。
おそらく多くの人はページネーションを利用すると思うので、今回のようにしておきました。
一通りの解説は終わったので今回はこれにて終了です。お疲れ様でした!