Laravel Blade Template Component Slot Layout
Laravel PHP

Laravel Blade Templates:Components, Slots, and Building Layouts

https://laravel.com/docs/8.x/blade

******* 以下文章是基於 Laravel 8 *******

Component & Slot 的概念從 Laravel 5.4 開始就有了,一直演進到目前是 Laravel 8。寫法上觀念上都有很大的改變,也看得出來 Laravel 前端 template 是越來越往 component based 的框架在走。前幾年開始到目前 (2021年5月),似乎 component based 的前端框架還是主流,這點可以再做觀察。

本篇文章主要是講 Laravel 8 的 Component & Slot。如果基礎 Laravel Blade Templates 還不熟的讀者,可以參考我之前的文章:Laravel Blade Templates:The Basics

首先,Component 分為兩種:class based component 和 anonymous component。簡單來說,class based component 是由一支 Class 檔案搭配一支 view template 檔案。而 anonymous component 則是簡單只有一支 view template 檔案。

Class based component

如上方 Laravel 8 官方文件,要建立一個 class based component,可以下這個指令

php artisan make:component Alert

這個指令會在

  • App\View\Components 新增一支 Class 檔案。
  • resources/views/components 新增一支 view template 檔案。

建立 class based component 時也可以建立檔案結構 (比較好管理)

php artisan make:component Forms/Input

這個指令會在

  • App\View\Components\Forms 新增一支 Class 檔案。
  • resources/views/components/forms 新增一支 view template 檔案。

調用端如何使用?用 x- 開頭接 component class name,以小寫 dash (-) 分隔的方式

<x-alert/>

<x-user-profile/>

如果 component 是有檔案結構,如上例 Forms/Input,除了小寫 dash (-) 分隔的規則,檔案上下層以 dot (.) 分隔

<x-forms.input/>

Component 如何接收調用端的資料

component class 用 constructor 接收調用端的資料 (下例並非完整,僅用於勾勒出 component 如何接收資料,完整 class 請參考官方文件)

class Alert extends Component
{
    ...

    public function __construct($type, $message)
    {
        $this->type = $type;
        $this->message = $message;
    }

    ...
}

這樣調用端就可以將 type, message 傳進 component。
注意傳 php 變數的屬性前要加 colon (:)

<x-alert type="error" :message="$message"/>

再看一下上方 class Alert。component class 的 view 可以直接使用 component class 的 public 變數,不用特別傳到 view 裡 (跟一般 Laravel controller 不同)。以上方 class Alert 來說,$type, $message 應該是 public 變數,class Alert 的 view 可以直接使用。

Component Attributes

所有不在 component constructor 接收的變數,都會自動存到 component 的 “attribute bag”,其實就是 component 預設的 $attributes 變數。所以如果沒有特別設定 component constructor,想直接顯示調用端所下的屬性,component view 可以這樣寫

<div {{ $attributes }}>
    <!-- Component content -->
</div>

很多時候,component view 本身可以設定 css class,同時也開放調用端下 css,可以使用 $attributes->merge

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>

還記得 class Alert,$type 變數是 constructor 接收。如果調用端這樣寫

<x-alert type="error" :message="$message" class="mb-4"/>

上面 $attributes->merge 就會把 class=”alert alert-error” 跟 “mb-4” 合併在一起,最後產出的 html 是

<div class="alert alert-error mb-4">
    <!-- Contents of the $message variable -->
</div>

html 屬性不是只有 css class,其他屬性不見得能下多個值。如果 $attributes->merge 這樣寫

<button {{ $attributes->merge(['type' => 'button']) }}>
    {{ $slot }}
</button>

當調用端 x-button 沒有指定 type,最後產出的 html button type=”button”。
當調用端 x-button 有指定 type,最後產出的 html button type=調用端指定的值。
所以非 css class 的屬性,$attributes->merge 的行為是「覆寫」,而非「合併」

Slot

除了上段討論的 $attributes 變數,component 也預設了 $slot 變數。如果 component view 是

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    {{ $slot }}
</div>

調用端 x-alert 中間夾的內容,會自動填到 $slot 變數中。但 component view 還有一個 $title,此時調用端就要新增一個 <x-slot name=”title”>,中間夾的內容會填入 $title (這個地方 title 也是一個 slot)

<x-alert>
    <x-slot name="title">
        Server Error
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>

slot 可以看成是一個比較快,不需要 component class,又可以將資料傳到 component 的機制。

Anonymous Component

anonymous component 不需要 component class,在 resources/views/components 新增一支 view template 檔案,調用端就可使用了 <x-your-component/>

resources/views/components 可以有檔案結構,調用端檔案上下層以 dot (.) 分隔,例如 <x-forms.input/>,這些都跟 class based component 相同。

因為沒有 component class,還是要有一個機制可以傳入指定變數。anonymous component 使用 @props directive,其中並可指定變數的預設值,例如下面 type 的預設值是 “info”

@props(['type' => 'info', 'message'])

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>

使用 Component 做版面 layout

Laravel 演進到現在 (Laravel 8),其實是鼓勵開發者用 component 做版面 layout,而不是用傳統的 @extends, @yield, @section 等。

用 component 做版面 layout,主要是上面提到的 slot 機制。例如 resources/views/components/layout.blade.php

<html>
    <head>
        <title>{{ $title ?? 'Todo Manager' }}</title>
    </head>
    <body>
        <h1>Todos</h1>
        <hr/>
        {{ $slot }}
    </body>
</html>

child view:resources/views/tasks.blade.php。其中 <x-layout> 中整段會填到 $slot,也注意到 title slot 的寫法,可以傳入 $title 變數。

<x-layout>
    <x-slot name="title">
        Custom Title
    </x-slot>

    @foreach ($tasks as $task)
        {{ $task }}
    @endforeach
</x-layout>

相較於傳統的 @extends, @yield, @section 佈局方式

  • child view 的角色是 component 的調用端
  • master view 的角色是 component 本身

如果對 design pattern 有了解的讀者,傳統的佈局方式跟 component based 的佈局方式,是類似的 design pattern,所以使用上邏輯是很像的。

也可以參考我 Design Pattern 系列的文章

結論

本文主要是希望能勾勒出 Laravel 8 component 的使用,與使用 component 做版面 layout 的方式。沒有談太多細節、或變化型的使用,很多細節的部分應該是由開發者自行斟酌使用。Laravel 前端目前看起來是往 component based 的方向在走,習慣於 @extends, @yield, @section 佈局方式的開發者,可以考慮使用 component。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。