【Laravel】 bladeのコンポーネントの使い方
bladeでは、ページの中にコンポーネントというページの一部分を切り取ったものを埋め込む利用できます。コンポーネントを利用することでヘッダーを共通化したり、サブビューを利用することでパーツを切り出して使い回すといった事ができます。
目次
コンポーネント
各Webページでは、ヘッダー、フッター、head
要素など、共通するパーツが数多く存在します。しかし、各Webページごとに同じコードを書くのは保守性が下がり、コードの肥大化も起こります。
コンポーネントを定義することで、各ビューのメイン部分だけを書けば良くなり、共通部分を1つにまとめることができます。
コンポーネントを作る
まずはコンポーネントを作ってみましょう。作ったコンポーネントは、他のコンポーネントから呼び出すことができます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Page</title> </head> <body> <header> <nav> <ul> <li>Home</li> <li>Search</li> </ul> </nav> </header> <main> {{ $slot }} </main> <footer> <!-- ... --> <p>Copyright ...</p> </footer> </body> </html>
{{ $slot }}
には、コンポーネントを使う側で定義した内容が挿入されます。
ファイル名は、コンポーネント呼び出し側の指定に用います。
php artisan make:component Hoge
のようにartisan
でもファイルを作成できます。これを使用すると、ファイルがresources/views
に1つ作成されるのと、app/View/Components
にコンポーネントが登録されます。
しかし、app/View/Components
にクラスを作らなくてもコンポーネントは使用できます。
クラスを用いたコンポーネントの使い方は、下の記事に載せています。(この内容を確認後に見ることをおすすめします)
コンポーネントを使う
作ったlayout.blade.php
を使って内容を表示してみましょう。
x-layout
要素が、レイアウトの{{ $slot }}
に入ります。
<!-- x-abcdeのように書くと、app/resources/views/components/ ディレクトリ内 abcde.blade.php のスロットに入れてくれる --> <!-- 今回はx-layoutなので、app/resources/views/components/layout.blade.php が使用される --> <x-layout> <p>{{ $hoge }}</p> </x-layout>
これで、
<header> <nav> <ul> <li>Home</li> <li>Search</li> </ul> </nav> </header> <main> <p>10</p> </main> <footer> <!-- ... --> <p>Copyright ...</p> </footer>
のような表示になっていれば成功です。
もし.../components/hoge/fuga.blade.php
のように、サブディレクトリに入ってるコンポーネントを利用したい場合、<x-hoge.fuga> ... </x-hoge.fuga>
のように、ドットで区切ります。
パスが長いので、app/resources/views
は...
で省略します。
スロットの名前
デフォルトのスロット名はslot
です。上のコードで{{ $slot }}
としたのは、デフォルト名であるために名前の指定なしで使えるためです。
名前を付ける場合、コンポーネント側では{{ $title }}
のようにし、($slot
と$attributes
以外) 呼び出し側では<x-layout name="title">Hogehoge</x-layout>
のように書きます。
<head> <title>{{ $title }}</title> </head> <body> <!-- ... --> <main> {{ $slot }} </main> <!-- ... --> </body>
<!-- x-layout は .../components/layout.blade.php を指す --> <x-layout> <!-- $title のところに入れる --> <x-slot name="title">Hogehoge</x-slot> <!-- $slotに入れる内容は裸で置く --> <p>{{ $hoge }}</p> <p>aaabbbccc</p> </x-layout>
呼び出される側のコンポーネントでは、同じスロット名を何度でも使えます。同じスロット名であれば同じ値が出力されます。
属性を送る
例えば、メッセージ表示を表示するコンポーネントを考えてみます。メッセージを表示する際は、単なる文章だけでなく、成功、警告、エラーなどの種類があり、それによってタグのclass
が変化するはずです。
slotがあるものとして<x-slot name="type">info</x-slot>
のように送っても良いですが、あまりに分かりづらいですよね。その場合、呼び出し側のタグに属性として入れて送ることができます。
<div class="alert alert-{{ $type }}"> {{ $message }} </div>
<x-message message="メッセージ内容" type="info" /> <!-- または --> <x-message message="メッセージ内容" type="info"></x-message>
コンポーネントを呼び出すタグの属性として、スロットを属性名、スロットの中身を値として入れることができます。
{{ $hogeFuga }}
のような場合、単語をハイフンで区切ります。$hogeFuga
なら呼び出し側ではhoge-fuga
と書きます。
属性を全て取得する
上の例では、特定の属性だけを利用しました。しかし、入ってきた属性全てを一気に使いたい場合があるかもしれません。その場合、コンポーネント側で$attribute
を使用します。
<div class="alert alert-{{ $type }}"> {{ $message }} {{ $attributes }} <!-- style="padding: 1rem;" message="メッセージ内容" type="info" が出力される --> </div>
<x-message style="padding: 1rem;" message="メッセージ内容" type="info" />
そのため、属性を全部設定したい場合は
<div {{ $attributes }}> {{ $message }} </div>
のように書くと、タグに入れた属性を全部設定してくれます。
属性とスロットで使い方がこんがらがりそうですが、
- 呼び出し側のタグの属性に入れたものはスロットにも使え、かつ
$attributes
の文字列にも含まれる - 呼び出し側のタグの中の要素に入れたものは、スロットに入る
ということです。つまり、<x-hoge style="color: #000;" />
があったとすると、コンポーネント側では{{ $style }}
で取得でき、かつ{{ $attributes }}
の文字列内にも含まれます。
属性のmerge
例えば$attributes
にclass
が含まれていたとする場合、つまり<div class="hoge" {{ $attributes }}>...</div>
ではうまく動作しません。$attributes
側のclass
が無視されます。
その場合、merge
を使用します。
<div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}> {{ $message }} </div>
class
をマージする際は、クラス名をキーに、そのクラスを適用するかどうかを値として入れることで、条件付きのマージが可能です。
<div {{ $attributes->class(['alert', "alert-{$type}", 'pd-0' => $hasPadding]) }}> {{ $message }} </div>
<x-message message="メッセージ内容" type="info" :has-padding="false" />
$hasPadding
がfalse
であるため、class
にpd-0
は付与されません。もちろん普通の条件式も書けるので、呼び出し側からは特定のキーにtrue
かfalse
を入れ、具体的なクラス名はコンポーネント側で処理する、といったことが可能になります。
has-padding="false"
だと、false
ではなく"false"
と解釈されます。PHPの値であることを明示し、booleanで送信するために:has-padding
としています。
上の例では、$attributes
に入ってる属性のclass
と、alert alert-{$type}
をmergeしています。これで両方の属性の値が使われます。
props
上の例では$attributes
にまとめて入れていましたが、@props
を使用することで切り出すことができ、初期値も設定できます。
<!-- 配列を入れる。キーと値のセットにすると初期値を入れられる --> @props([ 'type' => 'info', 'message' ]) <div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}> {{ $message }} {{ $attributes }} <!-- style="padding: 1rem;" だけが表示される--> </div>
<x-message style="padding: 1rem;" message="メッセージ内容" />
<div class="alert alert-{{ $type }}" {{ $attributes }}>...</div>
でも良いのですが、attributes
にclass
が入っていても、タグにclass
がすでに設定されているので無視されます。
PHPにおいて、配列と連想配列は同一の存在です。PHPにおける配列とは、キーが整数型で0, 1, 2…と連番となっている連想配列と言っても良いです。
ここで、@props
を使用する場合、中に出てくる全てのスロットを@props
の配列に入れる必要があります。1つでも欠けるとエラーとなるため、少々使い勝手は悪いです。
@props([ 'type' => 'info' ]) <div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}> <!-- Undefined variable $message --> {{ $message }} {{ $attributes }} </div>
属性に変数を入れて送る
例えば、<x-layout hoge="{{ $fuga }}"></x-layout>
としてもうまく送信されません。変数の値を送りたい場合、:
を属性名に付け、属性の値に変数名をそのまま入れます。
<x-message :message="$message" type="info" />
vueのpropsと似ていますね。
コンポーネントの呼び出し色々
上の例では、ビューとして使う場所からコンポーネントを呼び出していました。それだけでなく、コンポーネント呼び出しの中身にコンポーネント呼び出しを書くこともできます。
また、コンポーネントからコンポーネントを呼び出すこともできます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ $title }}</title> </head> <body> {{ $slot }} </body> </html>
<!-- コンポーネントから別のコンポーネント呼び出し --> <x-layout> <!-- layout.blade.php の $title に入れる内容の中身にスロットがある例 --> <x-slot name="title">{{ $title }} | AppName</x-slot> <!-- layout.blade.php の $slot に入れる内容 --> <main> {{ $slot }} </main> </x-layout>
@props([ 'type' => 'info', 'message' ]) <div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}> {{ $message }} {{ $attributes }} </div>
<x-contents> <!-- $title のところに入れる --> <x-slot name="title">Hogehoge</x-slot> <!-- x-layoutの$slotに入れる内容が別のコンポーネント等 --> <p>hoge</p> <x-message message="メッセージ内容" /> <p>fuga</p> </x-contents>
上の例では、index.blade.php
を起点として、components/contents.blade.php
とcomponents/message.blade.php
を呼び出しています。contents
からは更にcomponents/layout.blade.php
を呼び出しています。
- コンポーネントのスロットに入れる内容の中身にコンポーネントを入れられること
- コンポーネントからコンポーネントを呼び出せること
- コンポーネントへ入れる内容にスロットを入れられること
この3点を把握すればコンポーネントを十分に活用できると思います。
まとめ
今までのLaravelでは、@section
や@yield
などを使用したレイアウトを主に使用してきましたが、Laravel7で実装されたコンポーネントがこれからの主流になっていくと思われます。公式でも、Laravel8の時点でドキュメントに古いテンプレートの書き方は載っていません。
コンポーネントを駆使して、ページの各パーツを共通化していきましょう!
