LaravelDocs(中文)

Blade 樣板

Blade 是 Laravel 內建的簡單但強大的樣板引擎

介紹 (Introduction)

Blade 是 Laravel 內建的簡單但強大的樣板引擎。與某些 PHP 樣板引擎不同,Blade 並不會限制您在樣板中使用原生 PHP。事實上,所有 Blade 樣板都會被編譯成純 PHP 並快取直到被修改,這表示 Blade 幾乎不會為您的應用程式增加額外負擔。Blade 樣板檔案使用 .blade.php 副檔名,通常儲存在 resources/views 目錄中。

您可以透過全域的 view 輔助函式從路由或 controller 回傳 Blade 檢視。如同在 views 文件所提到,您可以使用 view 輔助函式的第二個參數將資料傳遞給 Blade 檢視:

Route::get('/', function () {
    return view('greeting', ['name' => 'Finn']);
});

使用 Livewire 強化 Blade (Supercharging Blade With Livewire)

想要將 Blade 樣板提升到更進階的互動介面,且減少前端框架的複雜性嗎?請查看 Laravel Livewire。Livewire 讓您能撰寫帶有動態功能的 Blade 元件,這些功能通常只有使用像是 React 或 Vue 的前端框架才容易實現。透過 Livewire,您可以在不需大量客戶端渲染或額外建置流程的情況下,建立現代且具反應性的前端介面。

顯示資料 (Displaying Data)

您可以在 Blade 檢視中透過將變數包在大括號來顯示傳入的資料。例如,給定下列路由:

Route::get('/', function () {
    return view('welcome', ['name' => 'Samantha']);
});

您可以如此顯示 name 變數的內容:

Hello, {{ $name }}.

[!NOTE] Blade 的 {{ }} echo 陳述式會自動透過 PHP 的 htmlspecialchars 函式進行轉義,以防範 XSS 攻擊。

您不只限於顯示傳入檢視的變數內容,亦可 echo 任意 PHP 函式的回傳值。事實上,您可以在 Blade echo 陳述式內放入任何您需要執行的 PHP 程式碼:

The current UNIX timestamp is {{ time() }}.

HTML 實體編碼 (Html Entity Encoding)

預設情況下,Blade(以及 Laravel 的 e 函式)會對 HTML 實體進行雙重編碼。如果您想要停用雙重編碼,可在 AppServiceProviderboot 方法中呼叫 Blade::withoutDoubleEncoding

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Blade::withoutDoubleEncoding();
    }
}

顯示未轉義的資料 (Displaying Unescaped Data)

預設情況下,Blade 的 {{ }} 陳述式會自動透過 PHP 的 htmlspecialchars 進行轉義。如果您不希望資料被轉義,可以使用下列語法:

Hello, {!! $name !!}.

[!WARNING] 在 echo 使用使用者提供的內容時務必小心。通常應使用經過轉義的 {{ }} 語法來避免顯示使用者輸入時發生 XSS。

Blade 與 JavaScript 框架 (Blade And Javascript Frameworks)

由於許多 JavaScript 框架也使用大括號來表示要在瀏覽器中呈現的表達式,您可以在表達式前加上 @ 符號以告知 Blade 保持該表達式不被處理。例如:

<h1>Laravel</h1>

Hello, @{{ name }}.

在此範例中,@ 符號會被 Blade 移除;但 {{ name }} 表達式會保留,讓您的 JavaScript 框架在前端進行渲染。

@ 符號也可以用來跳脫 Blade 指令:

{{-- Blade template --}}
@@if()

<!-- HTML output -->
@if()

呈現 JSON (Rendering Json)

有時您可能會將陣列傳遞到檢視,並希望將其以 JSON 呈現以初始化 JavaScript 變數。舉例:

<script>
    var app = <?php echo json_encode($array); ?>;
</script>

不過,您可以改用 Illuminate\Support\Js::from 方法指令來替代手動呼叫 json_encodefrom 方法接受與 PHP 的 json_encode 相同的參數,但會確保產出的 JSON 已正確轉義,以便在 HTML 引號內使用。from 方法會回傳一段 JSON.parse 的 JavaScript 陳述,用以在前端還原出物件或陣列:

<script>
    var app = {{ Illuminate\Support\Js::from($array) }};
</script>

較新的 Laravel 範本骨架包含 Js facade,可在 Blade 檢視中更方便地使用此功能:

<script>
    var app = {{ Js::from($array) }};
</script>

[!WARNING] 您應僅使用 Js::from 方法來渲染現有變數為 JSON。Blade 的解析基於正規表示式,嘗試將複雜表達式直接傳入指令可能導致意外錯誤。

@verbatim 指令 (The At Verbatim Directive)

如果在範本中大量顯示 JavaScript 變數,您可以使用 @verbatim 指令包住該區段,避免每個 Blade echo 前都加上 @ 符號:

@verbatim
    <div class="container">
        Hello, {{ name }}.
    </div>
@endverbatim

Blade 指令 (Blade Directives)

除了樣板繼承與顯示資料之外,Blade 也提供了便捷的語法糖來對應常見的 PHP 控制結構(例如條件與迴圈)。這些指令使得在模板中撰寫控制流程更清晰且易讀。

If 條件 (If Statements)

您可以使用 @if@elseif@else@endif 指令來建立 if 條件,這些指令與對應的 PHP 敘述行為相同:

@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif

為了方便,Blade 也提供 @unless 指令作為 if (! ...) 的語法糖:

@unless (Auth::check())
    You are not signed in.
@endunless

另外,@isset@empty 指令可作為 issetempty 的簡短寫法:

@isset($records)
    // $records 已定義且不為 null...
@endisset

@empty($records)
    // $records 為 "empty"...
@endempty

驗證相關指令 (Authentication Directives)

@auth@guest 指令可快速判斷目前使用者是否已通過認證或為訪客:

@auth
    // 使用者已驗證...
@endauth

@guest
    // 使用者尚未登入...
@endguest

如有需要,您也可以在指令中指定要檢查的 guard:

@auth('admin')
    // admin guard 已認證的使用者...
@endauth

環境判斷指令 (Environment Directives)

使用 @production 可以在生產環境中顯示特定內容:

@production
    // 僅在 production 環境顯示...
@endproduction

或使用 @env 檢查特定環境或多個環境:

@env('staging')
    // staging 環境專屬內容...
@endenv

@env(['staging', 'production'])
    // staging 或 production 環境顯示...
@endenv

區段指令 (Section Directives)

@hasSection 可用來判斷指定的 section 是否有內容:

@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>

    <div class="clearfix"></div>
@endif

如果想判斷某個 section 是否缺少內容,可使用 @sectionMissing

@sectionMissing('navigation')
    <div class="pull-right">
        @include('default-navigation')
    </div>
@endif

Session 指令 (Session Directives)

@session 指令可用來檢查一個 session 值是否存在。若存在,@session@endsession 之間的內容會被評估。在 @session 內可使用 $value 來顯示該 session 值:

@session('status')
    <div class="p-4 bg-green-100">
        {{ $value }}
    </div>
@endsession

Context 指令 (Context Directives)

@context 指令用於判斷是否有指定的 context 值存在。若存在,@context@endcontext 間的內容將會被評估,並可透過 $value 取得該值:

@context('canonical')
    <link href="{{ $value }}" rel="canonical">
@endcontext

Switch 條件 (Switch Statements)

您可以使用 @switch@case@break@default@endswitch 指令建立 switch 結構:

@switch($i)
    @case(1)
        First case...
        @break

    @case(2)
        Second case...
        @break

    @default
        Default case...
@endswitch

迴圈 (Loops)

Blade 也提供了對 PHP 迴圈結構的簡潔指令,行為與原生 PHP 一致:

@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor

@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse

@while (true)
    <p>I'm looping forever.</p>
@endwhile

[!NOTE] 在 foreach 迴圈中,您可以使用 loop 變數 取得如是否為第一或最後一筆等有用資訊。

在迴圈中,您也可以使用 @continue@break 跳過或終止迭代:

@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach

您也可以將條件直接寫在指令內:

@foreach ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach

Loop 變數 (The Loop Variable)

foreach 迴圈內,Blade 會提供一個 $loop 變數,包含許多有用的屬性,例如目前迭代的索引或是否為第一/最後一筆:

@foreach ($users as $user)
    @if ($loop->first)
        This is the first iteration.
    @endif

    @if ($loop->last)
        This is the last iteration.
    @endif

    <p>This is user {{ $user->id }}</p>
@endforeach

在巢狀迴圈中,您可以透過 parent 屬性存取父層的 $loop

@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            This is the first iteration of the parent loop.
        @endif
    @endforeach
@endforeach

$loop 提供的屬性包括:

| 屬性 | 說明 | | ------------------ | ------------------------------ | | $loop->index | 當前迭代的索引(從 0 開始) | | $loop->iteration | 當前迭代次數(從 1 開始) | | $loop->remaining | 迴圈中剩餘的迭代數量 | | $loop->count | 陣列或集合中總項目數 | | $loop->first | 是否為第一筆迭代 | | $loop->last | 是否為最後一筆迭代 | | $loop->even | 當前迭代是否為偶數 | | $loop->odd | 當前迭代是否為奇數 | | $loop->depth | 目前迴圈的巢狀層級 | | $loop->parent | 在巢狀迴圈中,父層的 loop 變數 |

條件式 class 與 style (Conditional Classes)

@class 指令可根據條件組合 CSS class。該指令接受一個陣列,鍵為 class(或 class 列表),值為布林表達式;若陣列元素使用數字鍵,該 class 將永遠包含:

@php
    $isActive = false;
    $hasError = true;
@endphp

<span @class([
    'p-4',
    'font-bold' => $isActive,
    'text-gray-500' => ! $isActive,
    'bg-red' => $hasError,
])></span>

<span class="p-4 text-gray-500 bg-red"></span>

相似地,@style 指令可條件式加入行內 CSS:

@php
    $isActive = true;
@endphp

<span @style([
    'background-color: red',
    'font-weight: bold' => $isActive,
])></span>

<span style="background-color: red; font-weight: bold;"></span>

其他 HTML 屬性輔助指令 (Additional Attributes)

為了方便,Blade 提供了多個簡短指令來輸出常見的 HTML 屬性:

  • @checked(condition):當條件為真時輸出 checked
  • @selected(condition):當條件為真時輸出 selected
  • @disabled(condition):當條件為真時輸出 disabled
  • @readonly(condition):當條件為真時輸出 readonly
  • @required(condition):當條件為真時輸出 required

範例:

<input
    type="checkbox"
    name="active"
    value="active"
    @checked(old('active', $user->active))
/>

包含子檢視 (Including Subviews)

@include 指令允許在一個檢視中包含另一個 Blade 檢視。所有在父檢視可用的變數,預設也會在被包含的檢視中可用:

<div>
    @include('shared.errors')

    <form>
        <!-- Form Contents -->
    </form>
</div>

您也可以傳遞一個陣列作為額外資料給被包含的檢視:

@include('view.name', ['status' => 'complete'])

若您嘗試 @include 的檢視不存在,Laravel 會拋出錯誤。若該檢視可能不存在,請使用 @includeIf

@includeIf('view.name', ['status' => 'complete'])

若您想在布林為真時包含檢視,使用 @includeWhen;若為假時才包含,使用 @includeUnless

@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])

要包含陣列中第一個存在的檢視,可使用 @includeFirst

@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])

[!WARNING] 請避免在 Blade 檢視中使用 __DIR____FILE__ 常數,因為它們會指向編譯後、快取的檢視位置。

為集合渲染檢視 (Rendering Views For Collections)

您可以使用 @each 指令在一行中結合迴圈與 include:

@each('view.name', $jobs, 'job')

@each 的第一個參數是每項要渲染的檢視;第二個為要迭代的陣列或集合;第三個為在每次迭代中指定當前項目的變數名。可選的第四個參數為陣列為空時要渲染的檢視:

@each('view.name', $jobs, 'job', 'view.empty')

[!WARNING] 透過 @each 渲染的檢視不會繼承父檢視的變數;若子檢視需要父檢視的變數,請改用 @foreach + @include

@once 指令 (The Once Directive)

@once 指令可讓某段內容在單次渲染循環中僅被評估一次。這對於在迴圈中渲染元件時,只想將一段 JavaScript 推到頁面 header 的情境特別有用:

@once
    @push('scripts')
        <script>
            // Your custom JavaScript...
        </script>
    @endpush
@endonce

@push / @prepend 常搭配使用的 @pushOnce@prependOnce 指令也很方便:

@pushOnce('scripts')
    <script>
        // Your custom JavaScript...
    </script>
@endPushOnce

若要在來自不同檔案的推入內容重複時只保留一次,可為 @pushOnce 提供獨一無二的識別字串:

@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce

原始 PHP (Raw Php)

在某些情況下,將 PHP 程式碼嵌入檢視會很有用。您可以使用 @php 指令在樣板中執行一段原生 PHP:

@php
    $counter = 1;
@endphp

或者,若只需引入一個 class,可使用 @use 指令:

@use('App\Models\Flight')

可透過第二個參數為引入的 class 設定別名:

@use('App\Models\Flight', 'FlightModel')

若有多個同命名空間的 class,可一次群組引入:

@use('App\Models\{Flight, Airport}')

@use 指令也支援引入 PHP 函式與常數:

@use(function App\Helpers\format_currency)
@use(const App\Constants\MAX_ATTEMPTS)

註解 (Comments)

Blade 也允許在檢視中定義註解。與 HTML 註解不同,Blade 註解不會出現在最終輸出的 HTML 中:

{{-- This comment will not be present in the rendered HTML --}}

元件 (Components)

元件與插槽提供類似 sections 與 layouts 的功能,但有些開發者會覺得元件的心智模型更易於理解。有兩種撰寫元件的方式:class-based 元件與匿名元件。

要建立 class-based 元件,可使用 make:component Artisan 指令。這會在 app/View/Components 目錄建立元件:

php artisan make:component Alert

該指令也會在 resources/views/components 目錄建立對應的檢視樣板。對於您自己的應用程式,元件會自動在 app/View/Componentsresources/views/components 目錄中被發現,通常不需額外註冊。

您也可以在子目錄中建立元件:

php artisan make:component Forms/Input

手動註冊套件元件 (Manually Registering Package Components)

若您正在開發包含 Blade 元件的套件,需要手動在 service provider 的 boot 方法中註冊元件:

use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    Blade::component('package-alert', Alert::class);
}

註冊後,可使用其標籤別名來渲染:

<x-package-alert/>

或使用 componentNamespace 方法依照慣例自動載入元件 class:

Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');

這樣就可以用 package-name:: 語法來渲染套件元件:

<x-nightshade::calendar />
<x-nightshade::color-picker />

渲染元件 (Rendering Components)

要顯示元件,可在 Blade 樣板中使用以 x- 開頭、後接元件 class 名稱(kebab-case)的標籤:

<x-alert/>
<x-user-profile/>

若元件位於 app/View/Components 的子目錄,可使用 . 表示目錄層級:

<x-inputs.button/>

若想條件式渲染元件,可在元件 class 中定義 shouldRender 方法;若回傳 false,元件將不會被渲染。

Index 元件 (Index Components)

當元件目錄名稱與檔案名稱相同時,Laravel 會自動將其視為「根」元件。例如 Card/Card.php 可直接使用 <x-card> 來渲染:

<x-card>
    <x-card.header>...</x-card.header>
    <x-card.body>...</x-card.body>
</x-card>

傳遞資料給元件 (Passing Data To Components)

您可以透過 HTML 屬性將資料傳遞給 Blade 元件。簡單的原始值可使用一般 HTML 屬性字串;PHP 表達式與變數則需使用 : 前綴的屬性:

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

元件的所有資料屬性都應在 class 建構子中定義。所有 public 屬性會自動在元件檢視中可用:

<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
    public function __construct(
        public string $type,
        public string $message,
    ) {}

    public function render(): View
    {
        return view('components.alert');
    }
}

渲染時,只要 echo 變數名稱即可顯示:

<div class="alert alert-{{ $type }}">
    {{ $message }}
</div>

命名慣例 (Casing)

元件建構子參數應使用 camelCase,在 HTML 屬性中則使用 kebab-case

<x-alert alert-type="danger" />

簡短屬性語法 (Short Attribute Syntax)

當屬性名稱與變數名稱相同時,可使用簡短語法:

{{-- 簡短語法... --}}
<x-profile :$userId :$name />

{{-- 等同於... --}}
<x-profile :user-id="$userId" :name="$name" />

跳脫屬性渲染 (Escaping Attribute Rendering)

由於某些 JavaScript 框架(如 Alpine.js)也使用冒號前綴,您可以使用雙冒號 :: 告知 Blade 該屬性不是 PHP 表達式:

<x-button ::class="{ danger: isDeleting }">
    Submit
</x-button>

元件方法 (Component Methods)

除了 public 屬性外,元件 class 上的任何 public 方法也可在元件樣板中呼叫:

<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
    {{ $label }}
</option>

額外依賴 (Additional Dependencies)

若元件需要 Laravel Service Container 的依賴,可在資料屬性之前列出,容器會自動注入:

public function __construct(
    public AlertCreator $creator,
    public string $type,
    public string $message,
) {}

隱藏屬性與方法 (Hiding Attributes And Methods)

若要防止某些 public 屬性或方法暴露給元件樣板,可將它們加入 $except 陣列:

protected $except = ['type'];

元件屬性 (Component Attributes)

我們已經了解如何傳遞資料屬性給元件;但有時您可能需要指定額外的 HTML 屬性,例如 class,這些屬性並非元件運作所必須。一般而言,您會希望將這些屬性向下傳遞到元件樣板的根元素。

所有不屬於元件建構子的屬性會自動加入元件的「attribute bag」,並透過 $attributes 變數在元件中可用:

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

預設與合併屬性 (Default Merged Attributes)

有時您可能需要指定屬性的預設值,或將額外值合併到元件的某些屬性中。可使用 attribute bag 的 merge 方法:

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

若元件這樣使用:

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

最終渲染的 HTML 會是:

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

條件式合併 class (Conditionally Merge Classes)

可使用 class 方法在條件為 true 時合併 class:

<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>
    {{ $message }}
</div>

若需同時合併其他屬性,可鏈結 merge 方法:

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

非 class 屬性的合併 (Non Class Attribute Merging)

對於非 class 的屬性,merge 方法提供的值會被視為「預設值」。與 class 不同的是,這些屬性不會合併,而是被覆蓋:

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

使用自訂 type 時:

<x-button type="submit">
    Submit
</x-button>

渲染結果為:

<button type="submit">
    Submit
</button>

若希望非 class 屬性也能合併(前置),可使用 prepends 方法:

<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
    {{ $slot }}
</div>

擷取與過濾屬性 (Filtering Attributes)

可使用 filter 方法過濾屬性:

{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}

或使用 whereStartsWith 取得以特定字串開頭的屬性:

{{ $attributes->whereStartsWith('wire:model') }}

相反地,whereDoesntStartWith 可排除以特定字串開頭的屬性:

{{ $attributes->whereDoesntStartWith('wire:model') }}

使用 first 方法可渲染 attribute bag 中的第一個屬性:

{{ $attributes->whereStartsWith('wire:model')->first() }}

使用 has 方法可檢查屬性是否存在:

@if ($attributes->has('class'))
    <div>Class attribute is present</div>
@endif

使用 get 方法可取得特定屬性的值:

{{ $attributes->get('class') }}

保留關鍵字 (Reserved Keywords)

以下關鍵字為 Blade 內部使用,不可作為元件的 public 屬性或方法名稱:

  • data
  • render
  • resolve
  • resolveView
  • shouldRender
  • view
  • withAttributes
  • withName

Slots(插槽)

您常常需要透過「slots」將額外內容傳遞給元件。元件插槽透過 echo $slot 變數來渲染:

<!-- /resources/views/components/alert.blade.php -->

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

透過將內容注入元件來傳遞 slot:

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

有時元件可能需要在不同位置渲染多個插槽。讓我們修改 alert 元件以支援「title」插槽:

<!-- /resources/views/components/alert.blade.php -->

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

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

使用 x-slot 標籤定義具名插槽的內容。不在 x-slot 內的內容會傳遞給 $slot 變數:

<x-alert>
    <x-slot:title>
        Server Error
    </x-slot>

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

可使用 isEmpty 方法判斷插槽是否有內容:

@if ($slot->isEmpty())
    This is default content if the slot is empty.
@else
    {{ $slot }}
@endif

Scoped Slots

若您曾使用過 Vue 等 JavaScript 框架,可能熟悉「scoped slots」概念。在 Laravel 中,可透過在元件上定義 public 方法或屬性,並在插槽中透過 $component 變數存取:

<x-alert>
    <x-slot:title>
        {{ $component->formatAlert('Server Error') }}
    </x-slot>

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

插槽屬性 (Slot Attributes)

與 Blade 元件一樣,您可以為插槽指定額外屬性(如 CSS class):

<x-card class="shadow-sm">
    <x-slot:heading class="font-bold">
        Heading
    </x-slot>

    Content

    <x-slot:footer class="text-sm">
        Footer
    </x-slot>
</x-card>

可透過插槽變數的 attributes 屬性來存取這些屬性:

@props([
    'heading',
    'footer',
])

<div {{ $attributes->class(['border']) }}>
    <h1 {{ $heading->attributes->class(['text-lg']) }}>
        {{ $heading }}
    </h1>

    {{ $slot }}

    <footer {{ $footer->attributes->class(['text-gray-700']) }}>
        {{ $footer }}
    </footer>
</div>

行內元件檢視 (Inline Component Views)

對於非常小的元件,同時管理 class 與檢視檔案可能會顯得繁瑣。您可以直接從 render 方法回傳元件的標記:

public function render(): string
{
    return <<<'blade'
        <div class="alert alert-danger">
            {{ $slot }}
        </div>
    blade;
}

要建立行內檢視元件,可在 make:component 指令使用 --inline 選項:

php artisan make:component Alert --inline

動態元件 (Dynamic Components)

有時您可能需要渲染一個在執行階段才能決定的元件。可使用 Laravel 內建的 dynamic-component

// $componentName = "secondary-button";

<x-dynamic-component :component="$componentName" class="mt-4" />

手動註冊元件 (Manually Registering Components)

若您正在撰寫套件或將元件放在非慣例目錄,需要手動在 service provider 的 boot 方法中註冊:

use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}

匿名元件 (Anonymous Components)

與行內元件類似,匿名元件也只需單一檔案即可管理,但匿名元件沒有對應的 class。只要在 resources/views/components 目錄中放一個 Blade 檔案即可定義匿名元件:

<x-alert/>

若元件位於 resources/views/components/inputs/button.blade.php,可用 . 表示子目錄:

<x-inputs.button/>

也可以透過 Artisan 建立匿名元件檢視:

php artisan make:component forms.input --view

匿名 Index 元件 (Anonymous Index Components)

當某元件是由多個 Blade 檔組成時,您可能想把這些檔案整理在同一個資料夾下。例如一個 accordion 元件:

/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php

這樣可以這樣使用:

<x-accordion>
    <x-accordion.item>
        ...
    </x-accordion.item>
</x-accordion>

Blade 允許您將名稱與目錄相同的檔案放在目錄裡,並將它視為該元件的「root」檢視:

/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php

匿名元件的資料屬性與 Attributes (Data Properties Attributes)

由於匿名元件沒有對應的 class,您可以透過 @props 指令指定哪些屬性應視為「資料變數」,其他屬性則會放入 attribute bag:

<!-- /resources/views/components/alert.blade.php -->

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

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

存取父元件資料:@aware (Accessing Parent Data)

有時候您會希望在子元件中存取父元件的屬性。可使用 @aware 指令:

<!-- /resources/views/components/menu/index.blade.php -->

@props(['color' => 'gray'])

<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
    {{ $slot }}
</ul>
<!-- /resources/views/components/menu/item.blade.php -->

@aware(['color' => 'gray'])

<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
    {{ $slot }}
</li>

[!WARNING] > @aware 只能存取以 HTML 屬性明確傳入父元件的資料,無法取得只在 @props 內設定、但未顯式傳入的預設值。

匿名元件路徑 (Anonymous Component Paths)

除了預設的 resources/views/components 目錄外,您也可以在 service provider 中註冊額外的匿名元件路徑:

public function boot(): void
{
    Blade::anonymousComponentPath(__DIR__.'/../components');
}

可選擇性地提供命名空間前綴:

Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');

渲染時即可使用:

<x-dashboard::panel />

建立版型 (Layouts)

使用元件的版型 (Layouts Using Components)

多數 Web 應用程式在各頁面之間都共享相同的版面結構。若每個檢視都重複整個 HTML 外框,維護將非常麻煩。幸好,我們可以使用單一 Blade 元件 來定義版型,並在整個應用中重複使用。

定義版型元件

<!-- resources/views/components/layout.blade.php -->

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

套用版型元件

<!-- resources/views/tasks.blade.php -->

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

如需自訂標題,可使用具名 slot:

<x-layout>
    <x-slot:title>
        Custom Title
    </x-slot>

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

使用樣板繼承的版型 (Layouts Using Template Inheritance)

定義版型 (Defining A Layout)

在 Components 出現之前,Blade 主要透過「樣板繼承」來建立版型:

<!-- resources/views/layouts/app.blade.php -->

<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            This is the master sidebar.
        @show

        <div class="container">
            @yield('content')
        </div>
    </body>
</html>

繼承版型 (Extending A Layout)

子檢視可透過 @extends 指令指定要繼承的版型,並在其中定義 @section 內容:

<!-- resources/views/child.blade.php -->

@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')
    @@parent

    <p>This is appended to the master sidebar.</p>
@endsection

@section('content')
    <p>This is my body content.</p>
@endsection

@yield 也可接受第二個參數作為預設內容:

@yield('content', 'Default content')

表單 (Forms)

CSRF 欄位 (Csrf Field)

所有 HTML 表單都應包含隱藏的 CSRF token 欄位,讓 CSRF 保護 middleware 能驗證請求。可使用 @csrf 指令產生此欄位:

<form method="POST" action="/profile">
    @csrf

    ...
</form>

HTTP Method 欄位 (Method Field)

由於 HTML 表單無法直接送出 PUTPATCHDELETE 請求,您可以透過隱藏欄位 _method 來「欺騙」HTTP 方法。@method 指令可以自動產生該欄位:

<form action="/foo/bar" method="POST">
    @method('PUT')

    ...
</form>

驗證錯誤 (Validation Errors)

@error 指令可快速檢查特定欄位是否有驗證錯誤訊息。在 @error 區塊中,可使用 $message 來顯示訊息:

<label for="title">Post Title</label>

<input
    id="title"
    type="text"
    class="@error('title') is-invalid @enderror"
/>

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

Stacks

Blade 允許您將內容推送到具名的「stack」,並在其他檢視或版型中一次渲染出來。這在指定子檢視所需的 JavaScript 或 CSS 資源時特別好用:

@push('scripts')
    <script src="/example.js"></script>
@endpush

若只想在條件為真時推入內容,可使用 @pushIf

@pushIf($shouldPush, 'scripts')
    <script src="/example.js"></script>
@endPushIf

您可以多次對同一個 stack 使用 @push。要在版型中渲染完整內容,可使用 @stack

<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>

若想將內容加在 stack 開頭,可使用 @prepend

@push('scripts')
    This will be second...
@endpush

@prepend('scripts')
    This will be first...
@endprepend

可使用 @hasstack 判斷 stack 是否有內容:

@hasstack('list')
    <ul>
        @stack('list')
    </ul>
@endif

Service Injection

@inject 指令可用來從 Laravel Service Container 解析服務,並將其注入為樣板中的變數:

@inject('metrics', 'App\\Services\\MetricsService')

<div>
    Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>

渲染行內 Blade 樣板 (Rendering Inline Blade Templates)

有時您可能只想將一小段 Blade 字串轉成 HTML,可使用 Blade::render

use Illuminate\\Support\\Facades\\Blade;

return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);

Laravel 會將這類臨時樣板寫入 storage/framework/views。若想在渲染後自動刪除快取檔,可傳入 deleteCachedView: true

return Blade::render(
    'Hello, {{ $name }}',
    ['name' => 'Julian Bashir'],
    deleteCachedView: true
);

渲染 Blade Fragments (Rendering Blade Fragments)

在搭配 Turbohtmx 等前端工具時,您可能只需要回傳 Blade 模板的一小部分。可使用 @fragment / @endfragment

@fragment('user-list')
    <ul>
        @foreach ($users as $user)
            <li>{{ $user->name }}</li>
        @endforeach
    </ul>
@endfragment

在渲染檢視時,可透過 fragment 只回傳特定區塊:

return view('dashboard', ['users' => $users])->fragment('user-list');

fragmentIf 可根據條件決定是否只回傳 fragment,否則回傳整個檢視:

return view('dashboard', ['users' => $users])
    ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');

fragments / fragmentsIf 則支援一次回傳多個 fragment,並將它們串接在一起:

view('dashboard', ['users' => $users])
    ->fragments(['user-list', 'comment-list']);

擴充 Blade (Extending Blade)

Blade 允許您使用 directive 方法定義自訂指令。當 Blade 編譯器遇到該指令時,會呼叫您提供的 callback,並將指令的表達式傳入:

use Illuminate\\Support\\Facades\\Blade;

Blade::directive('datetime', function (string $expression) {
    return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});

[!WARNING] 更新自訂指令邏輯後,必須清除快取檢視。可使用 php artisan view:clear

自訂 Echo Handler (Custom Echo Handlers)

當您在 Blade 中 echo 某個物件時,會呼叫該物件的 __toString 方法。不過,若您無法控制該方法(例如來自第三方套件的 class),可以使用 Blade::stringable 註冊自訂處理:

use Illuminate\\Support\\Facades\\Blade;
use Money\\Money;

Blade::stringable(function (Money $money) {
    return $money->formatTo('en_GB');
});

自訂 If 指令 (Custom If Statements)

若只需要簡單的自訂條件判斷,比起撰寫完整指令更方便的方式是使用 Blade::if

Blade::if('disk', function (string $value) {
    return config('filesystems.default') === $value;
});
``;

在樣板中即可使用:

```blade
@disk('local')
    <!-- The application is using the local disk... -->
@elsedisk('s3')
    <!-- The application is using the s3 disk... -->
@else
    <!-- The application is using some other disk... -->
@enddisk

@unlessdisk('local')
    <!-- The application is not using the local disk... -->
@enddisk