Blade 樣板
簡介
Blade 是 Laravel 內建的一個簡單但強大的樣板引擎。與其他 PHP 樣板引擎不同,Blade 不會在樣板中限制你不能使用純 PHP 程式碼。事實上,Blade 樣板會被編譯為純 PHP 程式碼,且在被修改前都會被快取起來。這代表,使用 Blade 並不會給你的網站帶來任何額外的開銷。Blade 樣板檔使用 .blade.php
副檔名,且通常放在 resources/views
目錄內。
在 Route 或 Controller 內,可以通過 view
全域輔助函式來回傳 Blade 樣板。當然,就像在 View 說明文件內講的一樣,使用 view
輔助函式的第二個引數,就可以將資料傳給 Blade View:
1Route::get('/', function () {2 return view('greeting', ['name' => 'Finn']);3});
1Route::get('/', function () {2 return view('greeting', ['name' => 'Finn']);3});
使用 Livewire 來增強 Blade 的功能
想讓你的 Blade 樣板更進一步增加功能,並輕鬆使用 Blade 製作動態界面嗎?請參考看看 Laravel Livewire。原本只能通過 React 或 Vue 等前端框架才能達成的動態功能,使用 Liveware 後,你只需要撰寫 Blade 元件就可以實現了,不需要增加專案複雜度、不需要使用前端轉譯、不用麻煩地處理建置各種 JavaScript 框架,就能建立現代、互動性的前端。
顯示資料
可以通過將變數以大括號包裝起來來顯示傳給 Blade View 的資料。舉例來說,假設有下列路由:
1Route::get('/', function () {2 return view('welcome', ['name' => 'Samantha']);3});
1Route::get('/', function () {2 return view('welcome', ['name' => 'Samantha']);3});
可以像這樣顯示 name
變數的內容:
1Hello, {{ $name }}.
1Hello, {{ $name }}.
Blade 的 {{ }}
echo 陳述式會自動通過 PHP 的 htmlspecialchars
函式來防止 XSS 攻擊。
在 Blade 中不只可以顯示傳進來的變數,還可以 echo 任何 PHP 函式的回傳值。事實上,可以在 Blade 的 echo 陳述式中放入任何的 PHP 程式碼:
1The current UNIX timestamp is {{ time() }}.
1The current UNIX timestamp is {{ time() }}.
HTML 實體編碼
預設情況下,Blade (以及 Laravel 的 e
函式) 會重複轉譯 HTML 實體 (Double Encode)。若不像被重複轉譯,請在 AppServiceProvider
內的 boot
方法中呼叫 Blade::withoutDoubleEncoding
方法:
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\Blade;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 Blade::withoutDoubleEncoding();16 }17}
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\Blade;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Bootstrap any application services.12 */13 public function boot(): void14 {15 Blade::withoutDoubleEncoding();16 }17}
顯示未逸出的資料
預設情況下,Blade 的 {{ }}
陳述式會自動通過 PHP 的 htmlspecialchars
函式來防止 XSS 攻擊。若不想要逸出資料,可以使用下列語法:
1Hello, {!! $name !!}.
1Hello, {!! $name !!}.
在輸出使用者提供的資料時,請格外小心。平常在顯示使用者提供的資料時應該要使用經過逸出的雙大括號語法來防止 XSS 攻擊。
Blade and JavaScript Frameworks
由於許多 JavaScript 框架都使用「大括號」來判斷給定的運算式應顯示在瀏覽器上,因此可以使用 @
符號來告訴 Blade 轉譯引擎不應修改該運算式。如:
1<h1>Laravel</h1>23Hello, @{{ name }}.
1<h1>Laravel</h1>23Hello, @{{ name }}.
在這個例子中,Blade 會將 @
符號逸出。而 {{ name }}
運算式則不會被 Blade 引擎處理,這樣一來便可讓 JavaScript 框架進行轉譯。
@
符號也可用來逸出 Blade 指示詞:
1{{-- Blade template --}}2@@if()34<!-- HTML output -->5@if()
1{{-- Blade template --}}2@@if()34<!-- HTML output -->5@if()
轉譯 JSON
有時候將陣列傳進 View 是為了將其轉譯為 JSON 來初始化 JavaScript 變數。如:
1<script>2 var app = <?php echo json_encode($array); ?>;3</script>
1<script>2 var app = <?php echo json_encode($array); ?>;3</script>
不過,比起手動呼叫 json_encode
,我們應使用 Illuminate\Support\Js::from
方法指示詞。from
方法接受的引數與 PHP 的 json_encode
函式相同;不過,from
方法會確保即使時在 HTML 引號內,也能正確逸出 JSON。from
方法會回傳一個 JSON.parse
JavaScript 陳述式字串,該陳述式會將給定的物件或陣列轉換為有效的 JavaScript 物件:
1<script>2 var app = {{ Illuminate\Support\Js::from($array) }};3</script>
1<script>2 var app = {{ Illuminate\Support\Js::from($array) }};3</script>
最新版本的 Laravel 專案 Skeleton 包含了一個 Js
Facade。使用這個 Facade 就能方便地在 Blade 樣板中存取這個功能:
1<script>2 var app = {{ Js::from($array) }};3</script>
1<script>2 var app = {{ Js::from($array) }};3</script>
請只在轉譯現有變數為 JSON 時使用 Js::from
方法。Blade 樣板引擎是是基於正規標示式實作的,若將複雜的陳述式傳給指示詞可能會導致未預期的錯誤。
@verbatim
指示詞
若在樣板中顯示了很多的 JavaScript 變數,則可以將 HTML 包裝在 @verbatim
指示詞內。這樣一來就不需要在每個 Blade 的 echo 陳述式前面加上前置 @
符號:
1@verbatim2 <div class="container">3 Hello, {{ name }}.4 </div>5@endverbatim
1@verbatim2 <div class="container">3 Hello, {{ name }}.4 </div>5@endverbatim
Blade 指示詞
除了繼承樣板與顯示資料外,Blade 也提供了常見 PHP 流程控制結構的方便捷徑,如條件陳述式以及迴圈。這些捷徑提供了一種非常乾淨簡介的方式來處理 PHP 流程控制,同時也保持了與 PHP 的相似性。
If 陳述式
可以通過 @if
, @elseif
, @else
與 @endif
指示詞來架構 if
陳述式。這些指示詞的功能與其 PHP 對應的部分相同:
1@if (count($records) === 1)2 I have one record!3@elseif (count($records) > 1)4 I have multiple records!5@else6 I don't have any records!7@endif
1@if (count($records) === 1)2 I have one record!3@elseif (count($records) > 1)4 I have multiple records!5@else6 I don't have any records!7@endif
為了方便起見,Blade 也提供了一個 @unless
指示詞:
1@unless (Auth::check())2 You are not signed in.3@endunless
1@unless (Auth::check())2 You are not signed in.3@endunless
除了已經討論過的條件指示詞外,也可以通過 @isset
與 @empty
指示詞來作為其對應 PHP 函式的方便捷徑:
1@isset($records)2 // $records is defined and is not null...3@endisset45@empty($records)6 // $records is "empty"...7@endempty
1@isset($records)2 // $records is defined and is not null...3@endisset45@empty($records)6 // $records is "empty"...7@endempty
認證指示詞
@auth
與 @guest
指示詞可以用來快速判斷目前的使用者是否已登入,或是該使用者是否為訪客:
1@auth2 // The user is authenticated...3@endauth45@guest6 // The user is not authenticated...7@endguest
1@auth2 // The user is authenticated...3@endauth45@guest6 // The user is not authenticated...7@endguest
若有需要,可以在使用 @auth
與 @guest
指示詞時指定要使用哪個認證 Guard 來做檢查:
1@auth('admin')2 // The user is authenticated...3@endauth45@guest('admin')6 // The user is not authenticated...7@endguest
1@auth('admin')2 // The user is authenticated...3@endauth45@guest('admin')6 // The user is not authenticated...7@endguest
環境指示詞
可以通過 @production
指示詞來判斷網站目前是否在正式環境上執行:
1@production2 // Production specific content...3@endproduction
1@production2 // Production specific content...3@endproduction
或者,可以通過 @env
指示詞來判斷網站是否在特定的環境上執行:
1@env('staging')2 // The application is running in "staging"...3@endenv45@env(['staging', 'production'])6 // The application is running in "staging" or "production"...7@endenv
1@env('staging')2 // The application is running in "staging"...3@endenv45@env(['staging', 'production'])6 // The application is running in "staging" or "production"...7@endenv
段落指示詞
可以通過 @hasSection
指示詞來判斷某個樣板繼承段落是否有內容:
1@hasSection('navigation')2 <div class="pull-right">3 @yield('navigation')4 </div>56 <div class="clearfix"></div>7@endif
1@hasSection('navigation')2 <div class="pull-right">3 @yield('navigation')4 </div>56 <div class="clearfix"></div>7@endif
可以通過 sectionMissing
指示詞來判斷某個段落是否沒有內容:
1@sectionMissing('navigation')2 <div class="pull-right">3 @include('default-navigation')4 </div>5@endif
1@sectionMissing('navigation')2 <div class="pull-right">3 @include('default-navigation')4 </div>5@endif
Session Directives
The @session
directive may be used to determine if a session value exists. If the session value exists, the template contents within the @session
and @endsession
directives will be evaluated. Within the @session
directive's contents, you may echo the $value
variable to display the session value:
1@session('status')2 <div class="p-4 bg-green-100">3 {{ $value }}4 </div>5@endsession
1@session('status')2 <div class="p-4 bg-green-100">3 {{ $value }}4 </div>5@endsession
Switch 陳述式
Switch 陳述式可以通過 @switch
, @case
, @break
, @default
與 @endswitch
指示詞來架構:
1@switch($i)2 @case(1)3 First case...4 @break56 @case(2)7 Second case...8 @break910 @default11 Default case...12@endswitch
1@switch($i)2 @case(1)3 First case...4 @break56 @case(2)7 Second case...8 @break910 @default11 Default case...12@endswitch
迴圈
除了條件陳述式外,Blade 也提供了能配合 PHP 的迴圈架構一起使用的一些簡單指示詞。同樣地,這些指示詞的功能都與其對應 PHP 的部分相同:
1@for ($i = 0; $i < 10; $i++)2 The current value is {{ $i }}3@endfor45@foreach ($users as $user)6 <p>This is user {{ $user->id }}</p>7@endforeach89@forelse ($users as $user)10 <li>{{ $user->name }}</li>11@empty12 <p>No users</p>13@endforelse1415@while (true)16 <p>I'm looping forever.</p>17@endwhile
1@for ($i = 0; $i < 10; $i++)2 The current value is {{ $i }}3@endfor45@foreach ($users as $user)6 <p>This is user {{ $user->id }}</p>7@endforeach89@forelse ($users as $user)10 <li>{{ $user->name }}</li>11@empty12 <p>No users</p>13@endforelse1415@while (true)16 <p>I'm looping forever.</p>17@endwhile
在使用 foreach
迴圈迭代時,可以使用迴圈變數來取得有關迴圈的有用資訊,如目前是否在迴圈的第一次或最後一次迭代。
在使用迴圈時,我們可以使用 @continue
與 @break
指示詞來跳過目前的迭代或終止迴圈:
1@foreach ($users as $user)2 @if ($user->type == 1)3 @continue4 @endif56 <li>{{ $user->name }}</li>78 @if ($user->number == 5)9 @break10 @endif11@endforeach
1@foreach ($users as $user)2 @if ($user->type == 1)3 @continue4 @endif56 <li>{{ $user->name }}</li>78 @if ($user->number == 5)9 @break10 @endif11@endforeach
也可以在指示詞定義中包含 continue 或 break 的條件:
1@foreach ($users as $user)2 @continue($user->type == 1)34 <li>{{ $user->name }}</li>56 @break($user->number == 5)7@endforeach
1@foreach ($users as $user)2 @continue($user->type == 1)34 <li>{{ $user->name }}</li>56 @break($user->number == 5)7@endforeach
迴圈變數
在迭代 foreach
迴圈時,迴圈內提供了 $loop
變數可用。這個變數提供了許多實用的資訊,如目前的迴圈索引,以及本次迭代是否為迴圈的第一次或最後一次迭代:
1@foreach ($users as $user)2 @if ($loop->first)3 This is the first iteration.4 @endif56 @if ($loop->last)7 This is the last iteration.8 @endif910 <p>This is user {{ $user->id }}</p>11@endforeach
1@foreach ($users as $user)2 @if ($loop->first)3 This is the first iteration.4 @endif56 @if ($loop->last)7 This is the last iteration.8 @endif910 <p>This is user {{ $user->id }}</p>11@endforeach
若在巢狀迴圈中,可以通過 parent
屬性來存取上層迴圈的 $loop
變數:
1@foreach ($users as $user)2 @foreach ($user->posts as $post)3 @if ($loop->parent->first)4 This is the first iteration of the parent loop.5 @endif6 @endforeach7@endforeach
1@foreach ($users as $user)2 @foreach ($user->posts as $post)3 @if ($loop->parent->first)4 This is the first iteration of the parent loop.5 @endif6 @endforeach7@endforeach
$loop
變數也包含了其他各種實用的屬性:
按條件顯示/隱藏 Class 與 Style
@class
指示詞可以有條件地編譯 CSS class 字串。@class
指示詞接受一組包含 class 的陣列,其中,陣列的索引鍵代表欲新增的 class,陣列值則是一個布林運算式。若陣列的元素有數字索引鍵,則該元素一定會被加到轉譯後的 Class 列表上:
1@php2 $isActive = false;3 $hasError = true;4@endphp56<span @class([7 'p-4',8 'font-bold' => $isActive,9 'text-gray-500' => ! $isActive,10 'bg-red' => $hasError,11])></span>1213<span class="p-4 text-gray-500 bg-red"></span>
1@php2 $isActive = false;3 $hasError = true;4@endphp56<span @class([7 'p-4',8 'font-bold' => $isActive,9 'text-gray-500' => ! $isActive,10 'bg-red' => $hasError,11])></span>1213<span class="p-4 text-gray-500 bg-red"></span>
類似地,@style
指示詞可用來依照條件在 HTML 元素內顯示或隱藏內嵌 CSS 樣式:
1@php2 $isActive = true;3@endphp45<span @style([6 'background-color: red',7 'font-weight: bold' => $isActive,8])></span>910<span style="background-color: red; font-weight: bold;"></span>
1@php2 $isActive = true;3@endphp45<span @style([6 'background-color: red',7 'font-weight: bold' => $isActive,8])></span>910<span style="background-color: red; font-weight: bold;"></span>
額外屬性
為了方便起見,可以使用 @checked
指示詞用來可輕鬆地標示給定 HTML 勾選框為「已勾選」。這個指示詞會在條件為 true
時 Echo checked
:
1<input2 type="checkbox"3 name="active"4 value="active"5 @checked(old('active', $user->active))6/>
1<input2 type="checkbox"3 name="active"4 value="active"5 @checked(old('active', $user->active))6/>
類似地,@selected
指示詞可用來表示給定 Select 選項應為「已選擇」:
1<select name="version">2 @foreach ($product->versions as $version)3 <option value="{{ $version }}" @selected(old('version') == $version)>4 {{ $version }}5 </option>6 @endforeach7</select>
1<select name="version">2 @foreach ($product->versions as $version)3 <option value="{{ $version }}" @selected(old('version') == $version)>4 {{ $version }}5 </option>6 @endforeach7</select>
此外,可使用 @disabled
指示詞來表示給定元素應為「disabled」:
1<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
1<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
此外,使用 @readonly
指示詞可用來表示給定元素應為「readonly」:
1<input2 type="email"3 name="email"5 @readonly($user->isNotAdmin())6/>
1<input2 type="email"3 name="email"5 @readonly($user->isNotAdmin())6/>
此外,@required
指示詞可用來表示給定元素應為「required」:
1<input2 type="text"3 name="title"4 value="title"5 @required($user->isAdmin())6/>
1<input2 type="text"3 name="title"4 value="title"5 @required($user->isAdmin())6/>
Include 子 View
雖然可以使用 @include
指示詞,但 Blade 的元件提供了類似的功能,且比起 @include
指示詞來說功能更強,可繫結資料與屬性。
Blade 的 @include
指示詞可用來在 Blade View 中包含另一個 View。所有上層 View 中可用的變數在 Include 的 View 當中都保持可用:
1<div>2 @include('shared.errors')34 <form>5 <!-- Form Contents -->6 </form>7</div>
1<div>2 @include('shared.errors')34 <form>5 <!-- Form Contents -->6 </form>7</div>
雖然 Include 的 View 會繼承其上層 View 中所有的資料,但也可以將要在包含的 View 中使用的資料作為陣列傳入:
1@include('view.name', ['status' => 'complete'])
1@include('view.name', ['status' => 'complete'])
若嘗試 @include
一個不存在的 View,Laravel 會擲回錯誤。若想 Include 一個可能不存在的 View,應使用 @includeIf
指示詞:
1@includeIf('view.name', ['status' => 'complete'])
1@includeIf('view.name', ['status' => 'complete'])
若想在某個布林表達式取值為 true
或 false
的時候 @include
一個 View,則可以使用 @includeWhen
與 @includeUnless
指示詞:
1@includeWhen($boolean, 'view.name', ['status' => 'complete'])23@includeUnless($boolean, 'view.name', ['status' => 'complete'])
1@includeWhen($boolean, 'view.name', ['status' => 'complete'])23@includeUnless($boolean, 'view.name', ['status' => 'complete'])
若要在某個包含了一些 View 的陣列中 Include 第一個存在的 View,可以使用 includeFirst
指示詞:
1@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
1@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
應避免在 Blade View 中使用 __DIR__
與 __FILE__
常數,因為這些常數會參照到經過快取與編譯過的 View。
Rendering Views for Collections
可以通過 Blade 的 @each
指示詞來將迴圈與 Include 組合成一行:
1@each('view.name', $jobs, 'job')
1@each('view.name', $jobs, 'job')
@each
指示詞的第一個引數是用來轉譯陣列或 Collection 中各個元素的 View。第二個引數則為要迭代的陣列或 Collection,而第三個引數則為要在 View 中被指派目前迭代的變數名稱。因此,舉例來說,若要迭代一個 jobs
陣列,通常我們會想在 View 中通過 job
變數來存取各個 Job。目前迭代的陣列索引鍵可在 View 中通過 key
存取。
也可以傳入第四個引數給 @each
指示詞。這個引數用來判斷當給定陣列為空時要被轉譯的 View。
1@each('view.name', $jobs, 'job', 'view.empty')
1@each('view.name', $jobs, 'job', 'view.empty')
通過 @each
所轉譯的 View 不會繼承其上層 View 的變數。若子 View 有需要這些變數,應使用 @foreach
與 @include
指示詞來代替。
@once
指示詞
@once
指示詞可以用來定義讓某部分的樣板在每個轉譯週期內只被轉譯一次。通常適用於想讓某部分的 JavaScript 的通過堆疊 Push 的頁面頭部時。舉例來說,若想在迴圈中轉譯某個給定的元素,可能會只想在第一次轉譯的時候將 JavaScript Push 到頭部:
1@once2 @push('scripts')3 <script>4 // Your custom JavaScript...5 </script>6 @endpush7@endonce
1@once2 @push('scripts')3 <script>4 // Your custom JavaScript...5 </script>6 @endpush7@endonce
由於 @once
指示詞常常與 @push
或 @prepend
指示詞一起使用,所以也提供了 @pushOnce
與 @prependOnce
等方便的指示詞可使用:
1@pushOnce('scripts')2 <script>3 // Your custom JavaScript...4 </script>5@endPushOnce
1@pushOnce('scripts')2 <script>3 // Your custom JavaScript...4 </script>5@endPushOnce
原始 PHP
在某些情況下,可能需要將 PHP 程式碼嵌入到 View 中。可以使用 Blade 的 @php
指示詞來在樣板中執行某一區塊的純 PHP:
1@php2 $counter = 1;3@endphp
1@php2 $counter = 1;3@endphp
Or, if you only need to use PHP to import a class, you may use the @use
directive:
1@use('App\Models\Flight')
1@use('App\Models\Flight')
A second argument may be provided to the @use
directive to alias the imported class:
1@use('App\Models\Flight', 'FlightModel')
1@use('App\Models\Flight', 'FlightModel')
註解
在 Blade 中,我們也可以在 View 中定義註解。不過,與 HTML 註解不同,Blade 的註解不會包含在網站所回傳的 HTML 中:
1{{-- This comment will not be present in the rendered HTML --}}
1{{-- This comment will not be present in the rendered HTML --}}
元件
元件與 Slot 提供了與 Section, Layout 與 Include 類似的功能。不過,有些人可能會覺得元件跟 Slot 比較好懂。撰寫元件有兩種方法:一種是基於類別的元件,另一種則是匿名元件。
若要建立基於類別的元件,可以使用 make:component
Artisan 指令。為了解釋如何使用元件,我們將會建立一個簡單的 Alert
元件。make:component
指令會將元件放在 app/View/Components
目錄中:
1php artisan make:component Alert
1php artisan make:component Alert
make:component
指令也會為元件建立一個 View 樣板。這個樣板會被放在 resources/views/components
目錄內。當在為專案撰寫元件時,元件會在 app/View/Components
與 resources/views/components
目錄中被 Auto Discover (自動偵測),因此不需要進一步註冊元件。
也可以在子資料夾內建立元件:
1php artisan make:component Forms/Input
1php artisan make:component Forms/Input
上述指令會在 app/View/Components/Forms
目錄內建立一個 Input
元件,而 View 會被放在 resources/views/components/forms
目錄內。
若想建立匿名元件 (即,只有 Blade 樣板且無類別的元件),可在叫用 make:component
指令時使用 --view
旗標:
1php artisan make:component forms.input --view
1php artisan make:component forms.input --view
上述指令會在 resources/views/components/forms/input.blade.php
中建立一個 Blade 檔,可通過 <x-forms.input />
來轉譯這個元件。
手動註冊套件元件
在為專案撰寫元件時,元件會在 app/View/Components
與 resources/views/components
目錄下被 Auto Discover (自動偵測)。
不過,若想製作使用 Blade 元件的套件,則需要手動註冊元件類別與其 HTML 標籤別名。通常,應在套件的 Service Provider 內的 boot
方法中註冊你的元件:
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap your package's services.5 */6public function boot(): void7{8 Blade::component('package-alert', Alert::class);9}
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap your package's services.5 */6public function boot(): void7{8 Blade::component('package-alert', Alert::class);9}
註冊好元件後,便可使用其標籤別名來轉譯:
1<x-package-alert/>
1<x-package-alert/>
或者,也可以使用 componentNamespace
方法來依照慣例自動載入元件類別。舉例來說,Nightshade
套件可能包含了放在 Package\Views\Components
Namespace 下的 Calendar
與 ColorPicker
元件:
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap your package's services.5 */6public function boot(): void7{8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');9}
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap your package's services.5 */6public function boot(): void7{8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');9}
這樣依賴可以讓套件元件通過其 Vendor Namespace 來使用 package-name::
語法:
1<x-nightshade::calendar />2<x-nightshade::color-picker />
1<x-nightshade::calendar />2<x-nightshade::color-picker />
Blade 會通過將元件名稱轉為 Pascal 命名法 (pascal-case) 來自動偵測與這個元件關連的類別。也可以使用「點」語法來支援子目錄。
轉譯元件
若要顯示元件,可以在 Blade 樣板中使用 Blade 元件的標籤。Blade 元件標籤以 x-
開頭,並接上以 Kebab Case 命名法命名的元素類別:
1<x-alert/>23<x-user-profile/>
1<x-alert/>23<x-user-profile/>
若元件類別在 app/View/Components
目錄中嵌套多層,可以使用 .
字元來標示巢狀目錄。舉例來說,假設有個位於 app/View/Components/Inputs/Button.php
的元件,我們可以像這樣轉譯該元件:
1<x-inputs.button/>
1<x-inputs.button/>
若想要有條件地轉譯元件,可在 Component 類別中定義 shouldRender
方法。若 shouldRender
方法回傳 false
,則該元件就不會被轉譯:
1use Illuminate\Support\Str;23/**4 * Whether the component should be rendered5 */6public function shouldRender(): bool7{8 return Str::length($this->message) > 0;9}
1use Illuminate\Support\Str;23/**4 * Whether the component should be rendered5 */6public function shouldRender(): bool7{8 return Str::length($this->message) > 0;9}
Index Components
Sometimes components are part of a component group and you may wish to group the related components within a single directory. For example, imagine a "card" component with the following class structure:
1App\Views\Components\Card\Card2App\Views\Components\Card\Header3App\Views\Components\Card\Body
1App\Views\Components\Card\Card2App\Views\Components\Card\Header3App\Views\Components\Card\Body
Since the root Card
component is nested within a Card
directory, you might expect that you would need to render the component via <x-card.card>
. However, when a component's file name matches the name of the component's directory, Laravel automatically assumes that component is the "root" component and allows you to render the component without repeating the directory name:
1<x-card>2 <x-card.header>...</x-card.header>3 <x-card.body>...</x-card.body>4</x-card>
1<x-card>2 <x-card.header>...</x-card.header>3 <x-card.body>...</x-card.body>4</x-card>
Passing Data to Components
可以使用 HTML 屬性來將資料傳給 Blade 元素。硬式編碼或原生值可以使用簡單的 HTML 屬性字串來傳給元素。PHP 表達式與變數應使用以 :
字元作為前綴的屬性來傳遞:
1<x-alert type="error" :message="$message"/>
1<x-alert type="error" :message="$message"/>
請在元件的類別建構函式中定義所有元件所需的資料屬性。元件中所有 Public 的屬性都會自動在元件的 View 中可用。不需要在元件的 render
方法中將這些資料傳給 View:
1<?php23namespace App\View\Components;45use Illuminate\View\Component;6use Illuminate\View\View;78class Alert extends Component9{10 /**11 * Create the component instance.12 */13 public function __construct(14 public string $type,15 public string $message,16 ) {}1718 /**19 * Get the view / contents that represent the component.20 */21 public function render(): View22 {23 return view('components.alert');24 }25}
1<?php23namespace App\View\Components;45use Illuminate\View\Component;6use Illuminate\View\View;78class Alert extends Component9{10 /**11 * Create the component instance.12 */13 public function __construct(14 public string $type,15 public string $message,16 ) {}1718 /**19 * Get the view / contents that represent the component.20 */21 public function render(): View22 {23 return view('components.alert');24 }25}
元件在進行轉譯時,可以通過 Echo 變數名稱來顯示元件的公用變數:
1<div class="alert alert-{{ $type }}">2 {{ $message }}3</div>
1<div class="alert alert-{{ $type }}">2 {{ $message }}3</div>
大小寫
元件建構函式中的引數應以 camelCase
來指定,而在 HTML 屬性中參照其引數名稱時應使用 kebab-case
。舉例來說,假設有下列元件建構函式:
1/**2 * Create the component instance.3 */4public function __construct(5 public string $alertType,6) {}
1/**2 * Create the component instance.3 */4public function __construct(5 public string $alertType,6) {}
可以像這樣將 $alertType
引數提供給元件:
1<x-alert alert-type="danger" />
1<x-alert alert-type="danger" />
簡短的屬性語法
將屬性傳入元件時,也可以使用「簡短屬性」語法。由於屬性名稱常常都與對應的變數名稱相同,因此此功能應該適用於大多數情況下:
1{{-- Short attribute syntax... --}}2<x-profile :$userId :$name />34{{-- Is equivalent to... --}}5<x-profile :user-id="$userId" :name="$name" />
1{{-- Short attribute syntax... --}}2<x-profile :$userId :$name />34{{-- Is equivalent to... --}}5<x-profile :user-id="$userId" :name="$name" />
逸出屬性轉譯
由於有些像 Alpine.js 的 JavaScript 框架也使用分號前綴的屬性,因此可以在 Blade 中使用雙分號(::
)來提示 Blade 其屬性並非 PHP 運算式。舉例來說,假設有下列元件:
1<x-button ::class="{ danger: isDeleting }">2 Submit3</x-button>
1<x-button ::class="{ danger: isDeleting }">2 Submit3</x-button>
Blade 會轉譯為下列 HTML:
1<button :class="{ danger: isDeleting }">2 Submit3</button>
1<button :class="{ danger: isDeleting }">2 Submit3</button>
元件方法
除了公用變數可以在元件樣板中使用以外,元件內的任何公用方法也可以被叫用。舉例來說,假設某個有 isSelected
方法的元件:
1/**2 * Determine if the given option is the currently selected option.3 */4public function isSelected(string $option): bool5{6 return $option === $this->selected;7}
1/**2 * Determine if the given option is the currently selected option.3 */4public function isSelected(string $option): bool5{6 return $option === $this->selected;7}
可以在元件樣板中通過叫用與方法名稱相同的變數來執行此方法:
1<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">2 {{ $label }}3</option>
1<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">2 {{ $label }}3</option>
Accessing Attributes and Slots Within Component Classes
Blade components also allow you to access the component name, attributes, and slot inside the class's render method. However, in order to access this data, you should return a closure from your component's render
method:
1use Closure;23/**4 * Get the view / contents that represent the component.5 */6public function render(): Closure7{8 return function () {9 return '<div {{ $attributes }}>Components content</div>';10 };11}
1use Closure;23/**4 * Get the view / contents that represent the component.5 */6public function render(): Closure7{8 return function () {9 return '<div {{ $attributes }}>Components content</div>';10 };11}
The closure returned by your component's render
method may also receive a $data
array as its only argument. This array will contain several elements that provide information about the component:
1return function (array $data) {2 // $data['componentName'];3 // $data['attributes'];4 // $data['slot'];56 return '<div {{ $attributes }}>Components content</div>';7}
1return function (array $data) {2 // $data['componentName'];3 // $data['attributes'];4 // $data['slot'];56 return '<div {{ $attributes }}>Components content</div>';7}
The elements in the $data
array should never be directly embedded into the Blade string returned by your render
method, as doing so could allow remote code execution via malicious attribute content.
componentName
與 HTML Tag 的 x-
前綴之後所使用的名稱相同。因此 <x-alert />
的 componentName
會是 alert
。attributes
元素會包含出現在 HTML 標籤上的所有屬性。slot
元素是一個 Illuminate\Support\HtmlString
實體,其中包含了該元件的 Slot 內容。
這個閉包應回傳字串。若該閉包回傳的字串為一個現有的 View,則會轉譯該 View。否則,回傳的字串將被轉換為內嵌的 Blade View。
額外的相依項
若元件需要從 Laravel 的 Service Container 內取得其他相依性,則可以將這些相依性列在所有元素的資料屬性前,Container 會自動插入這些相依性項目:
1use App\Services\AlertCreator;23/**4 * Create the component instance.5 */6public function __construct(7 public AlertCreator $creator,8 public string $type,9 public string $message,10) {}
1use App\Services\AlertCreator;23/**4 * Create the component instance.5 */6public function __construct(7 public AlertCreator $creator,8 public string $type,9 public string $message,10) {}
隱藏屬性與方法
若想防止一些公用方法或屬性被作為變數暴露到元件的樣板中,可以將這些項目加到元件的 $except
陣列屬性上:
1<?php23namespace App\View\Components;45use Illuminate\View\Component;67class Alert extends Component8{9 /**10 * The properties / methods that should not be exposed to the component template.11 *12 * @var array13 */14 protected $except = ['type'];1516 /**17 * Create the component instance.18 */19 public function __construct(20 public string $type,21 ) {}22}
1<?php23namespace App\View\Components;45use Illuminate\View\Component;67class Alert extends Component8{9 /**10 * The properties / methods that should not be exposed to the component template.11 *12 * @var array13 */14 protected $except = ['type'];1516 /**17 * Create the component instance.18 */19 public function __construct(20 public string $type,21 ) {}22}
元件屬性
我們已經看到了如何將資料屬性傳遞到元件內。然而,有時候可能會像指定不是元件運作所需的一些額外 HTML 屬性,如 class
。通常來說,我們會像將這些額外的屬性向下傳遞到元件樣板中的根元素。舉例來說,假設我們想要像這樣轉譯一個 alert
元件:
1<x-alert type="error" :message="$message" class="mt-4"/>
1<x-alert type="error" :message="$message" class="mt-4"/>
所有不在元件建構函式內的屬性都會被加到元件的「屬性包 (Attribute Bag)」內。這個屬性包會自動通過一個 $attributes
變數在元件內可用。可以通過 echo 這個變數來讓所有的屬性在元件內被轉譯:
1<div {{ $attributes }}>2 <!-- Component content -->3</div>
1<div {{ $attributes }}>2 <!-- Component content -->3</div>
目前不支援在元件標籤內使用如 @env
的指示詞。舉例來說,<x-alert :live="@env('production')"/>
將不會被編譯。
預設與合併屬性
有時候我們可能需要為屬性指定預設值,或是將額外的值合併到某些元件的屬性內。為此,可以使用屬性包的 merge
方法。這個方法特別適合用在如定義一系列永遠會被套用到元件上的預設 CSS:
1<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>2 {{ $message }}3</div>
1<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>2 {{ $message }}3</div>
若我們假設這個元件會被這樣使用:
1<x-alert type="error" :message="$message" class="mb-4"/>
1<x-alert type="error" :message="$message" class="mb-4"/>
則屬性最後經過轉譯的 HTML 會長這樣:
1<div class="alert alert-error mb-4">2 <!-- Contents of the $message variable -->3</div>
1<div class="alert alert-error mb-4">2 <!-- Contents of the $message variable -->3</div>
有條件地合併 Class
有時候我們可能會依照給定條件式的結果是否為 true
來決定要不要合併 Class。可以通過 class
方法來達成,該方法接受一段含有 Class 的陣列。陣列的索引鍵包含了要新增的 Class,而陣列的值則為布林運算式。若陣列的元素有數字索引鍵,則該元素一定會被加到轉譯後的 Class 列表上:
1<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>2 {{ $message }}3</div>
1<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>2 {{ $message }}3</div>
若像將其他屬性合併到元件上,可以將 merge
方法串接到 class
方法後:
1<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>2 {{ $slot }}3</button>
1<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>2 {{ $slot }}3</button>
若不想讓要套用條件式編譯 Class 的 HTML 收到經過合併的屬性,請使用 @class
指示詞。
非 Class 的屬性合併
在合併 class
以外的屬性值時,提供給 merge
方法的值會被當作是屬性的「預設」值。不過,與 class
屬性不同,這些屬性不會跟插入的屬性值合併,而是會被複寫。舉例來說,一個 button
元件的實作可能會長這樣:
1<button {{ $attributes->merge(['type' => 'button']) }}>2 {{ $slot }}3</button>
1<button {{ $attributes->merge(['type' => 'button']) }}>2 {{ $slot }}3</button>
若要以自訂 type
來轉譯按鈕元素,可以在使用元素時指定。若未指定 Type,則會使用 button
Type:
1<x-button type="submit">2 Submit3</x-button>
1<x-button type="submit">2 Submit3</x-button>
在這個範例中,button
元件轉譯後的 HTML 會是:
1<button type="submit">2 Submit3</button>
1<button type="submit">2 Submit3</button>
若像要讓 class
之外的屬性也能將其預設值與插入的值被串在一起,則可以使用 prepends
方法。在此例子中,data-controller
屬性將總是以 profile-controller
開頭,而任何額外插入的 data-controller
值都將被放在這個預設值之後:
1<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>2 {{ $slot }}3</div>
1<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>2 {{ $slot }}3</div>
Retrieving and Filtering Attributes
可以使用 filter
方法來過濾屬性。該方法接受一個閉包。若希望在屬性包內保留該屬性,則應在該閉包內回傳 true
:
1{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}
1{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}
為了方便起見,可以使用 whereStartsWith
方法來取得所有索引鍵以給定字串開頭的屬性:
1{{ $attributes->whereStartsWith('wire:model') }}
1{{ $attributes->whereStartsWith('wire:model') }}
相對的,可以使用 whereDoesntStartWith
方法來排除所有索引鍵不以給定字串開頭的屬性:
1{{ $attributes->whereDoesntStartWith('wire:model') }}
1{{ $attributes->whereDoesntStartWith('wire:model') }}
使用 first
方法,就可以轉譯給定屬性包中的第一個屬性:
1{{ $attributes->whereStartsWith('wire:model')->first() }}
1{{ $attributes->whereStartsWith('wire:model')->first() }}
若像檢查某個屬性是否有出現在元件內,可以使用 has
方法。這個方法接受一個屬性名稱作為其唯一的一個引數,並且會回傳一個布林值,來代表該屬性是否有出現:
1@if ($attributes->has('class'))2 <div>Class attribute is present</div>3@endif
1@if ($attributes->has('class'))2 <div>Class attribute is present</div>3@endif
若將陣列傳入 has
,則該方法會判斷該元件上是否具有所有給定的屬性:
1@if ($attributes->has(['name', 'class']))2 <div>All of the attributes are present</div>3@endif
1@if ($attributes->has(['name', 'class']))2 <div>All of the attributes are present</div>3@endif
hasAny
方法可用來判斷元件中是否有任一給定的屬性:
1@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))2 <div>One of the attributes is present</div>3@endif
1@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))2 <div>One of the attributes is present</div>3@endif
可以通過 get
方法來取得某個特定的屬性值:
1{{ $attributes->get('class') }}
1{{ $attributes->get('class') }}
保留字
預設情況下,Blade 中保留了一些關鍵字來作為內部使用,以用於轉譯元件。下列關鍵字將無法在元件內被定義為公用屬性或屬性名稱:
-
data
-
render
-
resolveView
-
shouldRender
-
view
-
withAttributes
-
withName
Slot
我們常常會通過「Slot」來將額外的內容傳到元件內。元件的 Slot 可以通過 Echo $slot
變數來進行轉譯。為了進一步探討這個概念,我們來想想有個長得像這樣的 alert
元件:
1<!-- /resources/views/components/alert.blade.php -->23<div class="alert alert-danger">4 {{ $slot }}5</div>
1<!-- /resources/views/components/alert.blade.php -->23<div class="alert alert-danger">4 {{ $slot }}5</div>
我們可以通過將內容插入到元件內來把內容傳給 slot
:
1<x-alert>2 <strong>Whoops!</strong> Something went wrong!3</x-alert>
1<x-alert>2 <strong>Whoops!</strong> Something went wrong!3</x-alert>
有時候,元件可能需要在元件中不同位置來轉譯多個不同的 Slot。我們來修改一下 alert 元件,讓這個元件能允許插入「title」Slot:
1<!-- /resources/views/components/alert.blade.php -->23<span class="alert-title">{{ $title }}</span>45<div class="alert alert-danger">6 {{ $slot }}7</div>
1<!-- /resources/views/components/alert.blade.php -->23<span class="alert-title">{{ $title }}</span>45<div class="alert alert-danger">6 {{ $slot }}7</div>
可以通過 x-slot
標籤來定義帶名稱 Slot 的內容。任何沒有明顯放在 x-slot
標籤內的內容都會被傳到元素的 $slot
變數內:
1<x-alert>2 <x-slot:title>3 Server Error4 </x-slot>56 <strong>Whoops!</strong> Something went wrong!7</x-alert>
1<x-alert>2 <x-slot:title>3 Server Error4 </x-slot>56 <strong>Whoops!</strong> Something went wrong!7</x-alert>
You may invoke a slot's isEmpty
method to determine if the slot contains content:
1<span class="alert-title">{{ $title }}</span>23<div class="alert alert-danger">4 @if ($slot->isEmpty())5 This is default content if the slot is empty.6 @else7 {{ $slot }}8 @endif9</div>
1<span class="alert-title">{{ $title }}</span>23<div class="alert alert-danger">4 @if ($slot->isEmpty())5 This is default content if the slot is empty.6 @else7 {{ $slot }}8 @endif9</div>
Additionally, the hasActualContent
method may be used to determine if the slot contains any "actual" content that is not an HTML comment:
1@if ($slot->hasActualContent())2 The scope has non-comment content.3@endif
1@if ($slot->hasActualContent())2 The scope has non-comment content.3@endif
限定範圍的 Slot
若讀者使用過如 Vue 之類的 JavaScript 框架,可能有看過「限定區域的 Slot (Scoped Slot)」。這種 Slot 可以讓我們能在 Slot 中從元件內存取資料或方法。在 Laravel 中,可以在元素內定義公用方法或屬性,然後在 Slot 內通過 $component
變數來存取元件,就可以達到類似的行為。在這個例子裡,我們會假設 x-alert
元件中有一個在元件類別內定義的公用 formatAlert
方法:
1<x-alert>2 <x-slot:title>3 {{ $component->formatAlert('Server Error') }}4 </x-slot>56 <strong>Whoops!</strong> Something went wrong!7</x-alert>
1<x-alert>2 <x-slot:title>3 {{ $component->formatAlert('Server Error') }}4 </x-slot>56 <strong>Whoops!</strong> Something went wrong!7</x-alert>
Slot 屬性
與 Blade 元件類似,我們可以將一些如 CSS Class 名稱等額外的屬性指派給 Slot:
1<x-card class="shadow-sm">2 <x-slot:heading class="font-bold">3 Heading4 </x-slot>56 Content78 <x-slot:footer class="text-sm">9 Footer10 </x-slot>11</x-card>
1<x-card class="shadow-sm">2 <x-slot:heading class="font-bold">3 Heading4 </x-slot>56 Content78 <x-slot:footer class="text-sm">9 Footer10 </x-slot>11</x-card>
若要與 Slot 屬性互動,可以存取 Slot 變數的 attributes
屬性。更多有關與屬性互動的資訊,請參考關於元件屬性的說明文件:
1@props([2 'heading',3 'footer',4])56<div {{ $attributes->class(['border']) }}>7 <h1 {{ $heading->attributes->class(['text-lg']) }}>8 {{ $heading }}9 </h1>1011 {{ $slot }}1213 <footer {{ $footer->attributes->class(['text-gray-700']) }}>14 {{ $footer }}15 </footer>16</div>
1@props([2 'heading',3 'footer',4])56<div {{ $attributes->class(['border']) }}>7 <h1 {{ $heading->attributes->class(['text-lg']) }}>8 {{ $heading }}9 </h1>1011 {{ $slot }}1213 <footer {{ $footer->attributes->class(['text-gray-700']) }}>14 {{ $footer }}15 </footer>16</div>
內嵌元件 View
對於非常小的元件,要同時處理元件類別與元件樣板感覺非常麻煩。為此,可以直接在 render
方法內回傳元件的標記:
1/**2 * Get the view / contents that represent the component.3 */4public function render(): string5{6 return <<<'blade'7 <div class="alert alert-danger">8 {{ $slot }}9 </div>10 blade;11}
1/**2 * Get the view / contents that represent the component.3 */4public function render(): string5{6 return <<<'blade'7 <div class="alert alert-danger">8 {{ $slot }}9 </div>10 blade;11}
產生內嵌 View 元件
若要建立會轉譯內嵌 View 的元件,可以在執行 make:component
指令時加上 inline
選項:
1php artisan make:component Alert --inline
1php artisan make:component Alert --inline
動態元件
有時候我們可能會需要轉譯元件,但在執行階段前並不知道要轉譯哪個元件。這種情況,可以使用 Laravel 的內建「dynamic-component」動態元件來依照執行階段的值或變數進行轉譯:
1// $componentName = "secondary-button";23<x-dynamic-component :component="$componentName" class="mt-4" />
1// $componentName = "secondary-button";23<x-dynamic-component :component="$componentName" class="mt-4" />
手動註冊元件
下列有關手動註冊元件的說明文件主要適用於撰寫包含 View 元件的 Laravel 套件的套件作者。若你並不撰寫套件,則這部分的元件說明文件可能跟你比較沒關係。
在為專案撰寫元件時,元件會在 app/View/Components
與 resources/views/components
目錄下被 Auto Discover (自動偵測)。
不過,若想製作使用 Blade 元件的套件或將元件放在不符合慣例的目錄內,則需要手動註冊元件類別與其 HTML 標籤別名,以讓 Laravel 知道要在哪裡尋找元件。通常,應在套件的 Service Provider 內的 boot
方法中註冊你的元件:
1use Illuminate\Support\Facades\Blade;2use VendorPackage\View\Components\AlertComponent;34/**5 * Bootstrap your package's services.6 */7public function boot(): void8{9 Blade::component('package-alert', AlertComponent::class);10}
1use Illuminate\Support\Facades\Blade;2use VendorPackage\View\Components\AlertComponent;34/**5 * Bootstrap your package's services.6 */7public function boot(): void8{9 Blade::component('package-alert', AlertComponent::class);10}
註冊好元件後,便可使用其標籤別名來轉譯:
1<x-package-alert/>
1<x-package-alert/>
自動載入套件元件
或者,也可以使用 componentNamespace
方法來依照慣例自動載入元件類別。舉例來說,Nightshade
套件可能包含了放在 Package\Views\Components
Namespace 下的 Calendar
與 ColorPicker
元件:
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap your package's services.5 */6public function boot(): void7{8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');9}
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap your package's services.5 */6public function boot(): void7{8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');9}
這樣依賴可以讓套件元件通過其 Vendor Namespace 來使用 package-name::
語法:
1<x-nightshade::calendar />2<x-nightshade::color-picker />
1<x-nightshade::calendar />2<x-nightshade::color-picker />
Blade 會通過將元件名稱轉為 Pascal 命名法 (pascal-case) 來自動偵測與這個元件關連的類別。也可以使用「點」語法來支援子目錄。
匿名元件
與內嵌元件類似,匿名元件提供了一種能在單一檔案內管理元件的機制。不過,匿名元件使用單一 View 檔案,且沒有相關聯的類別。若要定義匿名元件,只需要將 Blade 樣板放在 resources/views/components
目錄內即可。舉例來說,假設有在 resources/views/components/alert.blade.php
內定義個了一個元件,則可以輕鬆地像這樣轉譯該元件:
1<x-alert/>
1<x-alert/>
可以使用 .
字元來表示該元件是嵌套放在 components
目錄下的。舉例來說,假設某個元件是定義在 resources/views/components/inputs/button.blade.php
,則可以像這樣對其進行轉譯:
1<x-inputs.button/>
1<x-inputs.button/>
匿名的 Index 元件
有時候,若我們做了一個由多個 Blade 樣板組成的元件,我們可能會想將給定的元件樣板放在單一目錄內群組化起來。舉例來說,若有個「accordion」元件,並有下列目錄結構:
1/resources/views/components/accordion.blade.php2/resources/views/components/accordion/item.blade.php
1/resources/views/components/accordion.blade.php2/resources/views/components/accordion/item.blade.php
使用這個目錄結構能讓我們將 accordion 元件與其元素依照下列這種方式轉譯:
1<x-accordion>2 <x-accordion.item>3 ...4 </x-accordion.item>5</x-accordion>
1<x-accordion>2 <x-accordion.item>3 ...4 </x-accordion.item>5</x-accordion>
不過,若要使用 x-accordion
來轉譯 accordion 元件,則我們必須強制將「index」的 accordion 元件樣板放在 resources/views/components
目錄,而不是與其他 accordion 相關的樣板一起放在 accordion
目錄下。
Thankfully, Blade allows you to place a file matching the component's directory name within the component's directory itself. When this template exists, it can be rendered as the "root" element of the component even though it is nested within a directory. So, we can continue to use the same Blade syntax given in the example above; however, we will adjust our directory structure like so:
1/resources/views/components/accordion/accordion.blade.php2/resources/views/components/accordion/item.blade.php
1/resources/views/components/accordion/accordion.blade.php2/resources/views/components/accordion/item.blade.php
資料屬性
由於匿名元件沒有相關聯的類別,因此你可能像知道該如何判斷那些資料應作為變數傳給元件,而那些屬性應放在元件的 Attribute Bag 內。
可以通過在元件的 Blade 樣板最上方使用 @props
指示詞來指定那個屬性應被當作資料變數使用。在元件中,所有其他的屬性都會通過元件的屬性包內可用。若像為某個資料變數設定預設值,則可以指定變數的名稱作為陣列索引鍵,並以預設值作為陣列值:
1<!-- /resources/views/components/alert.blade.php -->23@props(['type' => 'info', 'message'])45<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>6 {{ $message }}7</div>
1<!-- /resources/views/components/alert.blade.php -->23@props(['type' => 'info', 'message'])45<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>6 {{ $message }}7</div>
以上方的元件定義為例,我們可能會像這樣來轉譯元件:
1<x-alert type="error" :message="$message" class="mb-4"/>
1<x-alert type="error" :message="$message" class="mb-4"/>
存取上層資料
有時候,我們會想從子元件中存取上層元件的資料。在這種情況下,可以使用 @aware
指示詞。舉例來說,假設我們正在建立一個有上層元件 <x-menu>
與子元件 <x-menu.item>
的複雜選單元件:
1<x-menu color="purple">2 <x-menu.item>...</x-menu.item>3 <x-menu.item>...</x-menu.item>4</x-menu>
1<x-menu color="purple">2 <x-menu.item>...</x-menu.item>3 <x-menu.item>...</x-menu.item>4</x-menu>
<x-menu>
元件可能會有像這樣的實作:
1<!-- /resources/views/components/menu/index.blade.php -->23@props(['color' => 'gray'])45<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>6 {{ $slot }}7</ul>
1<!-- /resources/views/components/menu/index.blade.php -->23@props(['color' => 'gray'])45<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>6 {{ $slot }}7</ul>
由於 color
屬性只傳給了上層元件 (<x-menu>
),因此該屬性在 <x-menu.item>
中將無法存取。不過,若我們使用了 @aware
指示詞,就可以讓該屬性也在 <x-menu.item>
內可用:
1<!-- /resources/views/components/menu/item.blade.php -->23@aware(['color' => 'gray'])45<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>6 {{ $slot }}7</li>
1<!-- /resources/views/components/menu/item.blade.php -->23@aware(['color' => 'gray'])45<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>6 {{ $slot }}7</li>
The @aware
directive cannot access parent data that is not explicitly passed to the parent component via HTML attributes. Default @props
values that are not explicitly passed to the parent component cannot be accessed by the @aware
directive.
匿名元件路徑
前面也提到過,若要定義匿名原件,一般是將 Blade 樣板放在 resources/views/components
目錄內。不過,有時候,我們可能會想向 Laravel 註冊預設路徑以外的其他路徑來放置匿名原件。
anonymousComponentPath
方法的第一個引數為匿名原件位置的「路徑」,而第二個可選引數則是該元件所要被放置的「Namespace」。一般來說,應在專案的某個 Service Provider 內 boot
方法中呼叫:
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Blade::anonymousComponentPath(__DIR__.'/../components');7}
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Blade::anonymousComponentPath(__DIR__.'/../components');7}
若像上述範例這樣,不指定前置詞來註冊元件路徑的話,在 Blade 元件時也就不需要使用對應的前置詞。舉例來說,若上述程式碼中註冊的路徑下有 panel.blade.php
元件的話,可以像這樣轉譯該元件:
1<x-panel />
1<x-panel />
也可以使用 anonymousComponentPath
方法的第二個引來提供前置詞「Namespace」:
1Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
1Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
提供前置詞時,若要轉譯這些放在「Namespace」下的元件,只要在該元件的名稱前方加上其 Namespace 即可:
1<x-dashboard::panel />
1<x-dashboard::panel />
製作 Layout
使用元件的 Layout
大多數網站都會在許多頁面間共用同一個相同 Layout (版面配置)。如果我們每建立一個 HTML 都要重寫一整個 Layout,就會變得非常麻煩又難維護。好佳在,我們可以非常輕鬆地把這個 Layout 定義為一個 Blade 元件,並在網站中重複利用。
Defining the Layout Component
舉例來說,假設我們正在製作一個「代辦事項」App。我們可能會像這樣定義一個 layout
元件:
1<!-- resources/views/components/layout.blade.php -->23<html>4 <head>5 <title>{{ $title ?? 'Todo Manager' }}</title>6 </head>7 <body>8 <h1>Todos</h1>9 <hr/>10 {{ $slot }}11 </body>12</html>
1<!-- resources/views/components/layout.blade.php -->23<html>4 <head>5 <title>{{ $title ?? 'Todo Manager' }}</title>6 </head>7 <body>8 <h1>Todos</h1>9 <hr/>10 {{ $slot }}11 </body>12</html>
Applying the Layout Component
定義好 layout
元件後,我們就可以建立使用該元件的 Blade 樣板。舉例來說,我們可以定義一個用來顯示任務清單的一個簡單的 View:
1<!-- resources/views/tasks.blade.php -->23<x-layout>4 @foreach ($tasks as $task)5 <div>{{ $task }}</div>6 @endforeach7</x-layout>
1<!-- resources/views/tasks.blade.php -->23<x-layout>4 @foreach ($tasks as $task)5 <div>{{ $task }}</div>6 @endforeach7</x-layout>
請記得,在 layout
元件,被插入的內容會被提供給預設的 $slot
變數。讀者可能已經注意到,我們的 layout
會在有提供 $title
Slot 時對其進行處理,並在未提供 $title
時顯示預設標題。我們也可以通過利用在元件說明文件中討論過的方法一樣,在任務清單 View 內通過標準的 Slot 語法來插入自訂標題。
1<!-- resources/views/tasks.blade.php -->23<x-layout>4 <x-slot:title>5 Custom Title6 </x-slot>78 @foreach ($tasks as $task)9 <div>{{ $task }}</div>10 @endforeach11</x-layout>
1<!-- resources/views/tasks.blade.php -->23<x-layout>4 <x-slot:title>5 Custom Title6 </x-slot>78 @foreach ($tasks as $task)9 <div>{{ $task }}</div>10 @endforeach11</x-layout>
現在我們已經定義好了畫面配置以及任務清單 View 了,接著只需要在路由內回傳 task
View:
1use App\Models\Task;23Route::get('/tasks', function () {4 return view('tasks', ['tasks' => Task::all()]);5});
1use App\Models\Task;23Route::get('/tasks', function () {4 return view('tasks', ['tasks' => Task::all()]);5});
使用樣板繼承的版面配置
Defining a Layout
也可以通過「樣板繼承」來製作 Layout。在元件功能問世前,我們通常都是使用這個方法來製作網站。
要開始使用樣板繼承的 Layout,我們先來看一個簡單的例子。首先,我們先來看看一個 Layout 定義。由於大多數的 Web App 都會在多個不同的頁面上共用同一個 Layout,因此將這個 Layout 定義為單一 Blade View 比較方便:
1<!-- resources/views/layouts/app.blade.php -->23<html>4 <head>5 <title>App Name - @yield('title')</title>6 </head>7 <body>8 @section('sidebar')9 This is the master sidebar.10 @show1112 <div class="container">13 @yield('content')14 </div>15 </body>16</html>
1<!-- resources/views/layouts/app.blade.php -->23<html>4 <head>5 <title>App Name - @yield('title')</title>6 </head>7 <body>8 @section('sidebar')9 This is the master sidebar.10 @show1112 <div class="container">13 @yield('content')14 </div>15 </body>16</html>
就像我們可以看到的一樣,這個檔案包含了普通的 HTML 標記。不過,可以注意到 @section
與 @yield
指示詞。@section
指示詞與其名稱代表的意思一樣,是定義一個內容的段落。而 @yield
指示詞則用來將給定段落的內容顯示出來。
現在,我們已經定義好要在網站中使用的 Layout 了。讓我們來定義繼承該 Layout 的子頁面。
Extending a Layout
在定義子 View 時,可以使用 @extends
Blade 指示詞來指定要「繼承」哪個 Layout。繼承了 Blade 版面配置的 View 可以使用 @section
指示詞來將內容插入到 Layout 的段落中。請記得,就像在剛才範例中看到的一樣,這些段落的內容會在 Layout 中通過 @yield
來顯示:
1<!-- resources/views/child.blade.php -->23@extends('layouts.app')45@section('title', 'Page Title')67@section('sidebar')8 @parent910 <p>This is appended to the master sidebar.</p>11@endsection1213@section('content')14 <p>This is my body content.</p>15@endsection
1<!-- resources/views/child.blade.php -->23@extends('layouts.app')45@section('title', 'Page Title')67@section('sidebar')8 @parent910 <p>This is appended to the master sidebar.</p>11@endsection1213@section('content')14 <p>This is my body content.</p>15@endsection
In this example, the sidebar
section is utilizing the @parent
directive to append (rather than overwriting) content to the layout's sidebar. The @parent
directive will be replaced by the content of the layout when the view is rendered.
相較於前一個例子,sidebar
段落是以 @endsection
結束的,而不是 @show
。@endsection
指示詞只會定義一個段落,而 @show
則會定義並 馬上 Yield 該段落。
@yield
指示詞也接受一個預設值作為其第二個參數。這個值會在要 Yield 的段落未定義時被轉譯:
1@yield('content', 'Default content')
1@yield('content', 'Default content')
表單
CSRF 欄位
只要是在專案中內定義 HTML 表單,不管是什麼時候,我們都應該要在表單內包含一個隱藏的 CSRF 權杖欄位來讓 CSRF 保護 Middleware 能認證請求。我們可以使用 @csrf
Blade 指示詞來產生這個權杖欄位:
1<form method="POST" action="/profile">2 @csrf34 ...5</form>
1<form method="POST" action="/profile">2 @csrf34 ...5</form>
方法欄位
由於 HTML 表單沒辦法建立 PUT
, PATCH
與 DELETE
請求,因此會需要加上一個隱藏的 _method
欄位來假裝成這些 HTTP 動詞。@method
Blade 指示詞可以幫你建立這個欄位:
1<form action="/foo/bar" method="POST">2 @method('PUT')34 ...5</form>
1<form action="/foo/bar" method="POST">2 @method('PUT')34 ...5</form>
認證錯誤
可以使用 @error
指示詞來快速檢查給定的屬性是否有驗證錯誤訊息。在 @error
指示詞內,可以 echo $message
變數來顯示錯誤訊息:
1<!-- /resources/views/post/create.blade.php -->23<label for="title">Post Title</label>45<input6 id="title"7 type="text"8 class="@error('title') is-invalid @enderror"9/>1011@error('title')12 <div class="alert alert-danger">{{ $message }}</div>13@enderror
1<!-- /resources/views/post/create.blade.php -->23<label for="title">Post Title</label>45<input6 id="title"7 type="text"8 class="@error('title') is-invalid @enderror"9/>1011@error('title')12 <div class="alert alert-danger">{{ $message }}</div>13@enderror
由於 @error
指示詞匯被編譯為「if」陳述式,因此我們可以使用 @else
指示詞來在屬性沒有錯誤時轉譯特定內容:
1<!-- /resources/views/auth.blade.php -->23<label for="email">Email address</label>45<input6 id="email"7 type="email"8 class="@error('email') is-invalid @else is-valid @enderror"9/>
1<!-- /resources/views/auth.blade.php -->23<label for="email">Email address</label>45<input6 id="email"7 type="email"8 class="@error('email') is-invalid @else is-valid @enderror"9/>
可以將特定錯誤包的名稱傳送給 @error
指示詞的第二個參數來在包含多個表單的頁面上取得驗證錯誤訊息:
1<!-- /resources/views/auth.blade.php -->23<label for="email">Email address</label>45<input6 id="email"7 type="email"8 class="@error('email', 'login') is-invalid @enderror"9/>1011@error('email', 'login')12 <div class="alert alert-danger">{{ $message }}</div>13@enderror
1<!-- /resources/views/auth.blade.php -->23<label for="email">Email address</label>45<input6 id="email"7 type="email"8 class="@error('email', 'login') is-invalid @enderror"9/>1011@error('email', 'login')12 <div class="alert alert-danger">{{ $message }}</div>13@enderror
堆疊
在 Blade 中可以 Push 到帶名稱的堆疊。這個堆疊可以在另一個 View 或 Layout 內進行轉譯。堆疊特別適合用來指定任何子 View 所需要的 JavaScript 函式庫:
1@push('scripts')2 <script src="/example.js"></script>3@endpush
1@push('scripts')2 <script src="/example.js"></script>3@endpush
若想要在某個布林運算式取值為 true
時才 @push
某段內容,可以使用 @pushIf
指示詞:
1@pushIf($shouldPush, 'scripts')2 <script src="/example.js"></script>3@endPushIf
1@pushIf($shouldPush, 'scripts')2 <script src="/example.js"></script>3@endPushIf
一個堆疊可以按照需求 Push 多次。要將完成的堆疊內容轉譯出來,只需要將堆疊名稱傳給 @stack
指示詞:
1<head>2 <!-- Head Contents -->34 @stack('scripts')5</head>
1<head>2 <!-- Head Contents -->34 @stack('scripts')5</head>
若想將內容加到堆疊的最前面,可以使用 @prepend
指示詞:
1@push('scripts')2 This will be second...3@endpush45// Later...67@prepend('scripts')8 This will be first...9@endprepend
1@push('scripts')2 This will be second...3@endpush45// Later...67@prepend('scripts')8 This will be first...9@endprepend
Service Injection
@inject
指示詞可以用來從 Laravel 的 Service Container 中取得服務。傳給 @inject
的第一個引數是要放置服務的變數名稱,而第二個引數則是要解析服務的類別或介面名稱:
1@inject('metrics', 'App\Services\MetricsService')23<div>4 Monthly Revenue: {{ $metrics->monthlyRevenue() }}.5</div>
1@inject('metrics', 'App\Services\MetricsService')23<div>4 Monthly Revenue: {{ $metrics->monthlyRevenue() }}.5</div>
轉譯內嵌的 Blade 樣板
有時候,我們可能會像將原始的 Blade 樣板字串轉譯為有效的 HTML。我們可以通過 Blade
Facade 所提供的 render
方法來達成。render
方法接受 Blade 樣板字串,以及一個用來提供給樣板的可選資料陣列:
1use Illuminate\Support\Facades\Blade;23return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
1use Illuminate\Support\Facades\Blade;23return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
Laravel 會將這些樣板寫到 storage/framework/views
來轉譯內嵌的 Blade 樣板。若想讓 Laravel 在轉譯完這些 Blade 樣板後刪除這些臨時檔案,可以將 deleteCachedView
引數提供給該方法:
1return Blade::render(2 'Hello, {{ $name }}',3 ['name' => 'Julian Bashir'],4 deleteCachedView: true5);
1return Blade::render(2 'Hello, {{ $name }}',3 ['name' => 'Julian Bashir'],4 deleteCachedView: true5);
轉譯 Blade 片段
使用如 Turbo 與 htmx 等前端框架時,我們偶爾只需要在 HTTP Response 中回傳一部分的 Blade 樣板即可。Blade 的「片段 (Fragment)」功能可實現這一行為。若要使用 Blade 片段,請將 Blade 樣板中的一部分放在 @fragment
與 @endfragment
指示詞中:
1@fragment('user-list')2 <ul>3 @foreach ($users as $user)4 <li>{{ $user->name }}</li>5 @endforeach6 </ul>7@endfragment
1@fragment('user-list')2 <ul>3 @foreach ($users as $user)4 <li>{{ $user->name }}</li>5 @endforeach6 </ul>7@endfragment
接著,在轉譯使用該樣板的 View 時,可以呼叫 fragment
方法來指定只在連外 HTTP Response 中包含特定的片段:
1return view('dashboard', ['users' => $users])->fragment('user-list');
1return view('dashboard', ['users' => $users])->fragment('user-list');
使用 fragmentIf
方法,就能依照給定條件來回傳 View Fragment。若不符合條件,則會回傳整個 View:
1return view('dashboard', ['users' => $users])2 ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
1return view('dashboard', ['users' => $users])2 ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
使用 fragments
與 fragmentsIf
方法,就能在 Response 中回傳多個 View Fragment。各個 Fragment 會被串接在一起:
1view('dashboard', ['users' => $users])2 ->fragments(['user-list', 'comment-list']);34view('dashboard', ['users' => $users])5 ->fragmentsIf(6 $request->hasHeader('HX-Request'),7 ['user-list', 'comment-list']8 );
1view('dashboard', ['users' => $users])2 ->fragments(['user-list', 'comment-list']);34view('dashboard', ['users' => $users])5 ->fragmentsIf(6 $request->hasHeader('HX-Request'),7 ['user-list', 'comment-list']8 );
擴充 Blade
Blade 中可以通過 directive
方法來自訂指示詞。當 Blade 編譯器遇到自訂指示詞的時候,編譯器會呼叫所提供的回呼,並將將該指示詞內包含的運算式提供給該回呼。
下列範例建立了一個 @datetime($var)
指示詞,用來將給定的 $var
格式化,而 $var
應為 DateTime
的實體:
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\Blade;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Register any application services.12 */13 public function register(): void14 {15 // ...16 }1718 /**19 * Bootstrap any application services.20 */21 public function boot(): void22 {23 Blade::directive('datetime', function (string $expression) {24 return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";25 });26 }27}
1<?php23namespace App\Providers;45use Illuminate\Support\Facades\Blade;6use Illuminate\Support\ServiceProvider;78class AppServiceProvider extends ServiceProvider9{10 /**11 * Register any application services.12 */13 public function register(): void14 {15 // ...16 }1718 /**19 * Bootstrap any application services.20 */21 public function boot(): void22 {23 Blade::directive('datetime', function (string $expression) {24 return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";25 });26 }27}
如你所見,我們接著會將 format
方法接到傳入指示詞的運算式之後。因此,在這個範例中,這個指示詞最終所產生的指示詞會長這樣:
1<?php echo ($var)->format('m/d/Y H:i'); ?>
1<?php echo ($var)->format('m/d/Y H:i'); ?>
更新完 Blade 的指示詞邏輯後,會需要刪除所有已快取的 Blade View。可以通過 view:clear
Artisan 指令來移除已快取的 Blade View。
自訂 Echo 處理常式
若有打算在 Blade 中「echo」某個物件,則 Blade 會叫用該物件的 __toString
方法。__toString
方法是 PHP 的其中一個「魔法方法」。不過,有的時候我們可能無法控制給定類別的 __toString
方法,如:來自第三方函式庫的類別。
這個時候,Blade 能讓我們針對特定類型的物件註冊自訂的 Echo 處理常式。為此,應叫用 Blade 的 stringable
方法。stringable
方法接受一個閉包,該閉包應在型別標示中指定要負責轉譯的物件。一般情況下來說,stringable
方法應在專案的 AppServiceProvider
類別中 boot
方法內叫用:
1use Illuminate\Support\Facades\Blade;2use Money\Money;34/**5 * Bootstrap any application services.6 */7public function boot(): void8{9 Blade::stringable(function (Money $money) {10 return $money->formatTo('en_GB');11 });12}
1use Illuminate\Support\Facades\Blade;2use Money\Money;34/**5 * Bootstrap any application services.6 */7public function boot(): void8{9 Blade::stringable(function (Money $money) {10 return $money->formatTo('en_GB');11 });12}
定義好自訂的 Echo 處理常式後,就可以直接在 Blade 樣板中 Echo 這個物件:
1Cost: {{ $money }}
1Cost: {{ $money }}
自訂 If 陳述式
為了定義一個簡單的自訂條件陳述式,撰寫一個自訂指示詞有時候搞得很複雜又不必要。為此,Blade 提供了一個 Blade::if
方法,可以讓你通過閉包來快速地定義自訂條件指示詞。舉例來說,讓我們來定義一個檢查專案設定的預設「disk」的自訂條件句。我們可以在 AppServiceProvider
中的 boot
方法內定義:
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap any application services.5 */6public function boot(): void7{8 Blade::if('disk', function (string $value) {9 return config('filesystems.default') === $value;10 });11}
1use Illuminate\Support\Facades\Blade;23/**4 * Bootstrap any application services.5 */6public function boot(): void7{8 Blade::if('disk', function (string $value) {9 return config('filesystems.default') === $value;10 });11}
定義好自訂條件句後,就可以在樣板中使用這個條件句:
1@disk('local')2 <!-- The application is using the local disk... -->3@elsedisk('s3')4 <!-- The application is using the s3 disk... -->5@else6 <!-- The application is using some other disk... -->7@enddisk89@unlessdisk('local')10 <!-- The application is not using the local disk... -->11@enddisk
1@disk('local')2 <!-- The application is using the local disk... -->3@elsedisk('s3')4 <!-- The application is using the s3 disk... -->5@else6 <!-- The application is using some other disk... -->7@enddisk89@unlessdisk('local')10 <!-- The application is not using the local disk... -->11@enddisk