Laravel 7.xの新コンポーネント機能!実例

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、Laravelのバージョン7.xがついに公開されるということで一足先にどのような機能が追加になったかをチェックしてみました。

いくつかLaravel 7.xには新しい機能が追加になっていますが、その中でもひときわ興味を引かれたのが、

Bladeの新コンポーネント機能

です。

正確に言うと、以前からBladeにはコンポーネント機能が存在していたのですが、今回はその機能が大幅に拡張され、いろいろと便利に使えるようになっていますので、今回の記事でまとめてみることにしました。

ぜひ皆さんのお役に立てると嬉しいです😊✨

【追記:2020.3.14】Laravel 7.1.2未満のバージョンにBlade Component由来のXSS(クロスサイトスクリプティング)の脆弱性が報告されています。できるだけ早くアップデートすることをおすすめします。

開発環境: Larave 7.x

何が一番変わったか

まず、これまでBladeコンポーネントは、以下のようにディレクティブを使って呼び出していました。

@component('components.search-box')
@endcomponent

これまでと同様この形も使えますが、今回のアップデートでは以下のような専用タグも使えるようになっています。

<x-search-box />

つまり、x-から始まってそれ以降はファイル名になります。(階層が深い場合は、ドットでつないで<x-boxes.search />という具合になります)

また、artisanコマンドに「コンポーネントを管理するクラス」を作成するmake:componentが追加になっています。

例えば、こんな感じで使います。

php artisan make:component SearchBox

では、1つずつ見ていきましょう!

コンポーネント・クラスをつくるartisanコマンド

先ほども書いたように、今回のアップデートで新しいartisanコマンドが追加され、コンポーネントを管理するクラスをつくることができますので、まずはこの方法でコンポーネントをつくってみたいと思います。

なお、今回は検索ボックスのためのコンポーネントを作ります。
以下のコマンドを実行してください。

php artisan make:component SearchBox

すると、/app/View/ComponentsフォルダにSearchBox.phpというファイルが作成されます。

そして、SearchBox.phprender()内で指定されているビューも以下のように/resources/views/componentsフォルダ内に自動的に作成されます。

では、この/resources/views/components/search-box.blade.phpを開いて以下のように検索ボックスを追加してください。

<!-- コンポーネント本体 -->
<form action="/search">
    <input type="text" name="q">
    <select>
        <option>▼以下から選択<option>
        <option value="book">本<option>
        <option value="music">音楽</option>
        <option value="game">ゲーム</option>
        <option value="food">食物</option>
        <option value="hobby">ホビー</option>
    </select>
    <button type="submit">検索</button>
</form>

これでコンポーネント・クラスの作成は完了です。
以下のようにコンポーネントを呼び出してブラウザから確認してみましょう。

<html>
<body>

    <x-search-box />

</body>
</html>

以下のように表示されました。

なお、シンプルなコンポーネントを使いたい場合、わざわざビューを用意するのはめんどうなものです。その場合は、以下のように--inlineオプションをつけるとrender()内にヒアドキュメントを使った簡易バージョンのコードを作成してくれます。(もちろんビューは作成されません)

php artisan make:component YourComponent --inline
public function render()
{
    return <<<'blade'
<div>
    (ここにコンポーネントの内容を用意してください)
</div>
blade;
}

コンポーネントへデータを送る

ここまででは、単にフォームを作っただけですので、毎回同じ内容が表示されることになりますが、次はコンポーネントにデータを送ってその内容に応じて表示内容を変化させてみましょう。

タグのプロパティを使う

では、まず<x-search-box />にHTMLタグのようなtitleプロパティを作ってタイトルが指定できるようにしてみましょう。

/app/View/Components/SearchBox.phpを開いて以下の部分を追加してください。

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class SearchBox extends Component
{
    public $title;

    /**
     * Create a new component instance.
     *
     * @return void
     */
    public function __construct($title)
    {
        $this->title = $title;
    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return view('components.search-box');
    }
}

これで、<x-search-box />タグでtitleプロパティが使えるようになりました。

<x-search-box title="検索フォーム" />

では、/resources/views/components/search-box.blade.phpの中でタイトルを表示できるようにしておきましょう。

<form action="/search">
    {{ $title }}<br>
    <input type="text" name="q">
    <select>
        <option>▼以下から選択<option>
        <option value="book">本<option>
        <option value="music">音楽</option>
        <option value="game">ゲーム</option>
        <option value="food">食物</option>
        <option value="hobby">ホビー</option>
    </select>
    <button type="submit">検索</button>
</form>

ブラウザで確認すると以下のように表示されます。

変数をコンポーネントに送る

今回のアップデートでこの部分が一番気をつけないといけないのですが、コンポーネントへ変数の内容を送る場合は、少し指定方法が変わっています。

これまでの流れで言うと、以下のように変数を指定したくなってしまいますが、これはうまくいきません。

<!-- 注意: これは間違った例です! -->

<x-search-box title="{{ $title }}" />

正解を言いますと、以下のように独自の書き方をしないといけません。(ちょっとVueに似たカンジですね)

<x-search-box :title="$title" />

クラス内からデータを送る

コンポーネント・クラスの中からでも簡単にコンポーネントへデータを送ることができます。

例えば、/app/View/Components/SearchBox.phpを開いてsections()というメソッドを追加してください。

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class SearchBox extends Component
{
    // 省略

    public function sections() {

        return [
            'book' => '本',
            'music' => '音楽',
            'game' => 'ゲーム',
            'food' => '食物',
            'hobby' => 'ホビー'
        ];

    }

    // 省略
}

実は、これで設定は完了です。(つまり、いちいちビューに変数としてバインディングしますよ、というコードを書かなくてもOKなんです😊✨)

では、/resources/views/components/search-box.blade.phpの中でsections()の中身を使ってみましょう。

<form action="/search">

    <!--省略-->

    <select>
        <option>▼以下から選択<option>
        @foreach($sections as $key => $name)
            <option value="{{ $key }}">{{ $name }}</option>
        @endforeach
    </select>
    <button type="submit">検索</button>
</form>

これでクラス内のsections()からデータを呼び出すことができます。

デフォルト値を判別した結果を送信する

例えば、先ほど作ったコードの中にはセレクトボックスがありましたが、この初期値を「ゲーム」にする例を見てみましょう。

/app/View/Components/SearchBox.phpを開いて中身を以下のようにしてください。

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class SearchBox extends Component
{
    public $selected; // 初期値(sectionsのkey)

    /**
     * Create a new component instance.
     *
     * @return void
     */
    public function __construct($selected)
    {
        $this->selected = $selected;
    }

    public function sections() {

        return [
            'book' => '本',
            'music' => '音楽',
            'game' => 'ゲーム',
            'food' => '食物',
            'hobby' => 'ホビー'
        ];

    }

    public function isSelected($option) {

        return $option === $this->selected;

    }

    /**
     * Get the view / contents that represent the component.
     *
     * @return \Illuminate\View\View|string
     */
    public function render()
    {
        return view('components.search-box');
    }
}

この中で重要なのが、__construct()$selectedを設定している部分とisSelected()の部分です。

まず、$selectedは初期値、つまり今回の場合では game が入ってくる予定になっています。

そして、isSelected()true / false の値をコンポーネント側に送ることになります。(なお、別にisSelected()ではなく、別の名前でも問題ありません)

では、コンポーネント側をみてみましょう。

<form action="/search">
    <input type="text" name="q">
    <select>
        <option>▼以下から選択<option>
        @foreach($sections as $key => $name)
            <option value="{{ $key }}" {{ $isSelected($key) ? 'selected="selected"' : '' }}>{{ $name }}</option>
        @endforeach
    </select>
    <button type="submit">検索</button>
</form>

<option>タグの中で三項演算子を使った条件分けがあるのがわかっていただけると思います。(ただし、$isSelected()$マークがついていることに注意してください)

そして、コンポーネントを呼び出す部分はこうなります。

<x-search-box selected="game" />

スタイルやクラスを設定する

例えば、コンポーネントにstyleclassなどのHTMLプロパティを直接使いたい、以下のような場合です。

<x-search-box style="background:#eee;padding:10px;" :title="$title" />

この場合、$attributesという変数を使うことで実装が可能です。

<form action="/search" {{ $attributes }}>

    <!-- 省略 -->

</form>

この場合、実際にレンダリングされるHTMLは次のとおりです。

<form action="/search" style="background:#eee;padding:10px;">

    <!-- 省略 -->

</form>

なお、$attributesは単なる変数ではなくComponentAttributeBagのインスタンスなので、例えば以下のようにデータを追加することもできます。

<form action="/search" {{ $attributes->merge(['class' => 'my-class']) }}>

    <!--省略-->

</form>

実際のHTMLはこうなります。

<form action="/search" class="my-class" style="background:#eee;padding:10px;">
    
    <!-- 省略 -->

</form>

$attributesには、only()except()などのメソッドも使えます。詳しくはIlluminate\View\ComponentAttributeBagをご覧ください。

スロットをつかう

今回の新しい<x-***** />タグでもスロットが使えます。

ひとつだけスロットを使う場合

スロットをひとつだけ使う場合は、とてもシンプルです。
例として送信ボタンをスロット化してみましょう。

<form action="/search">
    
    <!-- 省略 -->

    <!-- ↓↓↓ここにボタンが入ります -->
    {{ $slot }}
</form>

使い方はこうなります。

<x-search-box>
    <button type="submit">商品検索</button>
</x-search-box>

スロットを複数使う場合

スロットが複数必要な場合は、x-slotタグを使って各スロットに名前を付ける必要があります。

例を見てみましょう。

<x-search-box :title="$title">
    <x-slot name="title">タイトル</x-slot>
    <x-slot name="button">
        <button type="submit">商品検索</button>
    </x-slot>
</x-search-box>

そして、コンポーネント内ではそれぞれname="*****"の中身が変数として使えます。

<form action="/search">
    {{ $title }}
    
    <!-- 省略 -->

    {{ $button }}
</form>

実際のHTMLはこうなります。

<form action="/search">
    タイトル
    
    <!-- 省略 -->

    <button type="submit">商品検索</button>
</form>

無名コンポーネント

ここまでいろいろと紹介してきましたが、今回のアップデートでシンプルなコンポーネントの場合、実はコンポーネント・クラスを作る必要はなく、単に/resources/views/components内にファイルを用意するだけでOKです。

例として、今日の日付を表示するだけのシンプルなコンポーネント「today」を作ってみましょう。

/resources/views/components/today.blade.phpというファイルを作って中身を以下のようにします。

{{ date('Y-m-d') }}

使い方はこうなります。

<x-today />

どうでしょう。
これだけで簡単にコンポーネントが使えるようになるわけです。

また、もちろんデータをコンポーネント側に送信することもできて、例えば挨拶文をコンポーネントで表示する場合は以下のようにするとOKです。

{{ $greeting }}
{{ date('Y-m-d') }}
<x-today greeting="こんにちは。" />

実際にブラウザで表示するとこうなります。

なお、この無名コンポーネントで気をつけないといけないのが、「どれがプロパティでどれが変数の送信かわからなくなる」という部分です。

例えば、以下の例を見てください。

<x-today style="font-weight:bold;" greeting="こんにちは。" />

これは先ほどの例にstyleを追加したものですが、これを$attributesを使って以下のようにプロパティを取得しようとすると、少し問題が発生します。

<div {{ $attributes }}>
    {{ $greeting }}
    {{ date('Y-m-d') }}
</div>

実際のHTMLはこうなります。

<div style="font-weight:bold;" greeting="こんにちは。">
    こんにちは。
    2020-03-02
</div>

そうです。greetingまでstyleのように使われてしまうわけです。

これを解決するためにあるのが、@props()です。
今回の例で言うと以下のようにすることでgreetingを除外することができます。

@props(['greeting'])

<div {{ $attributes }}>
    {{ $greeting }}
    {{ date('Y-m-d') }}
</div>

出力結果はこうなりました。

<div style="font-weight:bold;">
    こんにちは。
    2020-03-02
</div>
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで今回は、Laravel 7.xの新しいコンポーネントについてご紹介しました。

専用のタグをHTMLと同じように使えるようになるので、デザイナーさんが見ても保守がしやすくなるんじゃないでしょうか。

また、もちろんコンポーネントは使い回しがしやすいので一度いいものをつくっておけばあとで楽ができることになります。

ぜひ皆さんも将来のために頑張ってコンポーネントを作って見てくださいね。

ではでは〜!

このエントリーをはてなブックマークに追加       follow us in feedly