💭

【Laravel】レイアウトの共通化、コンポーネント、スロットの使い方

2023/07/07に公開

開発環境

こちらで構築した環境を使用しています。
https://zenn.dev/nenenemo/articles/46d43854cd01c5

レイアウトの共通化

resources/viewsに全ページ共通のレイアウトを記述するlayouts/app.blade.phpを作成してください。

mkdir -p resources/views/layouts &&
touch resources/views/layouts/app.blade.php
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">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>@yield('title')</title>
    <!-- Fonts -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
    <!-- Styles -->
    @vite('resources/css/app.css')
</head>

<body>
    <header>
        @include('layouts.header')
    </header>

    <main>
        @yield('content')
    </main>

    <footer>
        @include('layouts.footer')
    </footer>
</body>
	
</html>

今回はヘッダーとフッターも共通のレイアウトを適用するので、layouts/header.blade.phplayouts/footer.blade.phpを作成してください。

touch resources/views/layouts/footer.blade.php resources/views/layouts/header.blade.php

それぞれのbladeを次のように編集してください。

header.blade.php
<div class="max-w-7xl bg-sky-200 mx-auto py-6 px-4 sm:px-6 lg:px-8">
    header
</div>
footer.blade.php
<div class="max-w-7xl bg-orange-300 mx-auto py-6 px-4 sm:px-6 lg:px-8">
    footer
</div>

create.blade.phpというファイルで下記のように記述してみます。

create.blade.php
@extends('layouts.app')

@section('title', '新規作成')

@section('content')
    <form method="POST" action="{{ route('user.store') }}">
        @csrf
        <div>
            <h1 class='text-center font-bold '>新規作成</h1>
            <div class="mt-4">
                <label for="name">名前</label>
                <input id="name" type="text" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" name="name" value="{{ old('name') }}" required>
                @error('name')
                    <div>{{ $message }}</div>
                @enderror
            </div>

            <div class="mt-4">
                <label for="email">メールアドレス</label>
                <input id="email" type="email" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" name="email" value="{{ old('email') }}" required>
                @error('email')
                    <div>{{ $message }}</div>
                @enderror
            </div>

            <div class="flex items-center justify-center my-4">
                <button type="submit">
                    登録を完了する
                </button>
            </div>
    </form>
@endsection

次のように表示されます。先ほどのレイアウトが継承されているのがわかりますね。

また、レイアウトファイル(layouts/app.blade.php)内で@yield('title')が使われる場所に、'新規作成'というタイトルが表示されているのがわかると思います。

@extends

指定されたテンプレートを継承し、それを基に新しいテンプレートを作成します。

@include

指定したファイルを組み込むことができます。

@yield

セクションを定義することができます。

@section

セクション名を定義することができます。

componentsファイルの作成

componentsファイルを作成します。
今回はボタンのコンポーネントを作成したいと思います。

php artisan make:component Button

#sail環境
sail artisan make:component <コンポーネント名>

app/view/componentsにファイルが作成されているか確認してください。
以下はbuttonのコンポーネントファイルです。

button.blade.php
<button
    {{ $attributes->merge(['type' => 'submit', 'class' => 'bg-gray-300']) }}>
    {{ $slot }}
</button>

$attributes->merge()

親コンポーネントで渡された属性とmergeメソッド内で指定された追加の属性をマージするために使用されます。

componentsを使うには、<x-ファイル名>と記述します。

create.blade.php
@extends('layouts.app')

@section('title', '新規作成')

@section('content')
    <form method="POST" action="{{ route('user.store') }}">
        @csrf
        <div>
            <h1 class='text-center font-bold '>新規作成</h1>
            <div class="mt-4">
                <label for="name">名前</label>
                <input id="name" type="text"
                    class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                    name="name" value="{{ old('name') }}" required>
                @error('name')
                    <div>{{ $message }}</div>
                @enderror
            </div>

            <div class="mt-4">
                <label for="email">メールアドレス</label>
                <input id="email" type="email"
                    class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
                    name="email" value="{{ old('email') }}" required>
                @error('email')
                    <div>{{ $message }}</div>
                @enderror
            </div>

            <div class="flex items-center justify-center my-4">
                <x-button>
                    登録を完了する
                </x-button>
            </div>
    </form>
@endsection

buttonタグをコンポーネントを使用する記述に変更しています。

components/button.blade.php
@props(['value'])

<button>
    {{ $value }}
</button>

文言を変更してみます。

create.blade.php
<x-button :value="__('文言を変更しました。')" />

@props(['value'])

@propsディレクティブを使用して、コンポーネントが受け取るプロパティを定義しています。@props(['value'])は、このコンポーネントがvalueという名前のプロパティを受け取ることを宣言しています。

ちなみに、<x-label :value="$buttonText">のように記述すると変数の値を表示することもできます。

スロット

下記で{{ $slot }}という記述が出てきました。これをスロットといいます。
$slotの部分はスロットに$contentsのように名前を付けても問題ないです。

<x-button>コンポーネントのタグ内のコンテンツ(文言を変更しました。
)が、コンポーネント内のスロットに挿入され、最終的にはボタンの文言として表示されます。

つまり、スロットを使用することで、Bladeコンポーネントの外部からコンテンツを注入し、動的なコンポーネントを作成することができます。

components/button.blade.php
<button>
    {{ $slot }}
</button>

文言を変更してみます。

create.blade.php
 <x-button>
文言を変更しました。
</x-button>

@component

@component<x-コンポーネント名>の違いは、LaravelのBladeコンポーネントと呼ばれる機能を使用するかどうかです。

create.blade.php
 @component('components.button', ['type' => 'primary', 'size' => 'large', 'disabled' => false])
文言を変更しました。
@endcomponent

@slot

Bladeコンポーネント内でスロットを定義するためにBladeテンプレート内で@componentと組み合わせて使用されます。

components/button.blade.php
<button>
    {{ $text }}
</button>

文言を変更してみます。

create.blade.php
@component('components.button')
    @slot('text')
        文言を変更しました。
    @endslot
@endcomponent

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion