【Laravel】 bladeのコンポーネントの使い方

bladeでは、ページの中にコンポーネントというページの一部分を切り取ったものを埋め込む利用できます。コンポーネントを利用することでヘッダーを共通化したり、サブビューを利用することでパーツを切り出して使い回すといった事ができます。

コンポーネント

各Webページでは、ヘッダー、フッター、head要素など、共通するパーツが数多く存在します。しかし、各Webページごとに同じコードを書くのは保守性が下がり、コードの肥大化も起こります。

コンポーネントを定義することで、各ビューのメイン部分だけを書けば良くなり、共通部分を1つにまとめることができます。

コンポーネントを作る

まずはコンポーネントを作ってみましょう。作ったコンポーネントは、他のコンポーネントから呼び出すことができます。

app/resources/views/components/layout.blade.php
<!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 }}に入ります。

app/resources/views/index.blade.php
<!-- 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>

これで、

result
<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>のように書きます。

…/components/layout.blade.php
<head>
    <title>{{ $title }}</title>
</head>
<body>
    <!-- ... -->
    <main>
        {{ $slot }}
    </main>
    <!-- ... -->
</body>
…/index.blade.php
<!-- 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>のように送っても良いですが、あまりに分かりづらいですよね。その場合、呼び出し側のタグに属性として入れて送ることができます。

…/components/message.blade.php
<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

例えば$attributesclassが含まれていたとする場合、つまり<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" />

$hasPaddingfalseであるため、classpd-0は付与されません。もちろん普通の条件式も書けるので、呼び出し側からは特定のキーにtruefalseを入れ、具体的なクラス名はコンポーネント側で処理する、といったことが可能になります。

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>でも良いのですが、attributesclassが入っていても、タグに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と似ていますね。

コンポーネントの呼び出し色々

上の例では、ビューとして使う場所からコンポーネントを呼び出していました。それだけでなく、コンポーネント呼び出しの中身にコンポーネント呼び出しを書くこともできます。

また、コンポーネントからコンポーネントを呼び出すこともできます。

…/components/layout.blade.php
<!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>
…/components/contents.blade.php
<!-- コンポーネントから別のコンポーネント呼び出し -->
<x-layout>
    <!-- layout.blade.php の $title に入れる内容の中身にスロットがある例 -->
    <x-slot name="title">{{ $title }} | AppName</x-slot>
    
    <!-- layout.blade.php の $slot に入れる内容 -->
    <main>
        {{ $slot }}
    </main>
</x-layout>
…/components/message.blade.php
@props([
    'type' => 'info',
    'message'
])

<div {{ $attributes->merge(['class' => "alert alert-{$type}"]) }}>
    {{ $message }}
    {{ $attributes }}
</div>
…/index.blade.php
<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.phpcomponents/message.blade.phpを呼び出しています。contentsからは更にcomponents/layout.blade.phpを呼び出しています。

  • コンポーネントのスロットに入れる内容の中身にコンポーネントを入れられること
  • コンポーネントからコンポーネントを呼び出せること
  • コンポーネントへ入れる内容にスロットを入れられること

この3点を把握すればコンポーネントを十分に活用できると思います。

まとめ

今までのLaravelでは、@section@yieldなどを使用したレイアウトを主に使用してきましたが、Laravel7で実装されたコンポーネントがこれからの主流になっていくと思われます。公式でも、Laravel8の時点でドキュメントに古いテンプレートの書き方は載っていません。

コンポーネントを駆使して、ページの各パーツを共通化していきましょう!

laravel component thumb

役に立ったらシェアしよう!