--- tags: laravel --- # 視圖的Blade版型 ![](https://i.imgur.com/FhLCuxC.jpg) ![](https://i.imgur.com/MMszhp2.jpg) ![](https://i.imgur.com/6j0KbUF.jpg) ## 簡介 Blade 是 Laravel 提供的一個簡單而又強大的模板引擎。和其他流行的 PHP 模板引擎不同在於Blade 並不限制你在視圖中使用原生 PHP 代碼。所有 Blade 視圖文件都將被編譯成原生的 PHP 程式碼並緩存起來,除非它被修改,否則不會重新編譯。這意味着 Blade 不太會給你的應用增加任何負擔。Blade 視圖文件使用.blade.php 作為文件副檔名,被存放在 resources/views 資料夾 Blade 視圖可被路由或控制器利用 view() 全域函式來回傳。就如同先前在視圖章節談過的,可以用view() 的第二參數來傳遞參數 ``` Route::get('/', function () { return view('greeting', ['name' => 'Finn']); }); ``` > 在了解更多的 Blade 細節前,最好先看過[視圖View](/MtZIWSCPR6KIqWPUCSvkmA)章節為宜 ## 顯示資料 你能夠透過在參數外面包上兩個大括號來顯示參數資料。比如有這樣的路由: ``` Route::get('/', function () { return view('welcome', ['name' => 'Samantha']); }); ``` 你能夠這樣寫來顯示參數name的內容 `Hello, {{ $name }}.` Blade的雙大括號語法會將所包的參數自動透過 PHP 的 htmlspecialchars() ,這個方法會排除掉所有可執行的內容,所以可以避免 XSS 攻擊 你也並非只能透過這種方式來將內容顯示在視圖上,你還是能透過任何的 PHP 函式來顯示結果。事實上你也能夠在 {{ }} 裏頭加上 PHP 程式碼 比如要顯示當前 UNIX timestamp 可以寫 {{ time() }} ### 渲染 JSON 有時為了初始化一個 JavaScript 變數,您可能會向視圖傳入一個陣列並將其轉化成 JSON 。例如: ``` <script> var app = <?php echo json_encode($array); ?>; </script> ``` 當然你也可使用 @json Blade 指令來代替手動呼叫 json_encode()。 @json 指令的參數和 PHP 的 json_encode() 一致: ``` <script> var app = @json($array); var app = @json($array, JSON_PRETTY_PRINT); </script> ``` > 你應該只使用 @json 指令來渲染已存在的參數變成 JSON,Blade 模板是基於正則表達式的,如果嘗試將一個複雜表達式傳遞給 @json 指令可能會導致無法預測的錯誤 ### HTML 實體編碼 預設情況下, Blade (以及 Laravel 的 e() )將會對 HTML 實體進行雙重編碼。如果你想要關閉這個行為,可以在 AppServiceProvider 中的 boot() 中呼叫 Blade::withoutDoubleEncoding(): ``` <?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Blade::withoutDoubleEncoding(); } } ``` #### 顯示不排除可執行的內容 前面說過,Blade {{ }} 指令會自動透過 htmlspecialchars() 來避免 XSS 攻擊,假如你不想要排除這些可執行的內容,比如 HTML標籤,可以使用以下指令: `Hello, {!! $name !!}.` > 要非常小心的使用上面這個語法,你應該只在取用自己資料時才用這個語法。如果要顯示由用戶所提供的資料,請務必使用 {{ }} 語法來防止 XSS 攻擊 ### Blade & JavaScript 框架 由於許多 JavaScript 框架也使用「大括號」來標示將顯示在瀏覽器中的表達式。因此,你可以使用 @ 符號來表示 Blade 渲染引擎應跳過這段不予處理,例如: ``` <h1>Laravel</h1> Hello, @{{ name }}. ``` 在這個例子中, @ 符號將被 Blade 移除;接著 Blade 將不會修改 {{ name }} 表達式,取而代之的是 JavaScript 模板來對其進行渲染 @ 符號也同樣可用於跳過 Blade 指令: ``` {{-- Blade template --}} @@json() <!-- HTML output --> @json() ``` #### The @verbatim 指令 如果您在模板中需要顯示很多 JavaScript 參數,可以將 HTML 包在 @verbatim 指令中。這樣您就不需要在每一個 Blade 指令前都添加 @ 符號: ``` @verbatim <div class="container"> Hello, {{ name }}. </div> @endverbatim ``` ## Blade 指令 除了版型的繼承與顯示資料外,Blade也提供方便的指令來取代 PHP 控制結構,諸如條件判斷式與迴圈。這些方便指令提供了一種非常乾淨的方式來與 PHP 的控制結構協作,而且語法上也與 PHP 非常相近 ### If 語句 你可以使用 @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 指令 ``` @unless (Auth::check()) You are not signed in. @endunless ``` 除了已經討論過的條件指令外, @isset 和 @empty 指令亦可作為它們所對應的 PHP 方法的便利方式: ``` @isset($records) // $records is defined and is not null... @endisset @empty($records) // $records is "empty"... @endempty ``` #### 驗證指令 @auth 和 @guest 指令可用於快速判斷當前用戶是否已經獲得登入授權或是當前用戶是訪客: ``` @auth // The user is authenticated... @endauth @guest // The user is not authenticated... @endguest ``` 如有必要你亦可在使用 @auth 和 @guest 指令时去指定要使用哪個驗證 Guard : ``` @auth('admin') // The user is authenticated... @endauth @guest('admin') // The user is not authenticated... @endguest ``` #### 環境指令 你可以使用 @production 指令來判斷應用是否處於生產環境: ``` @production // Production specific content... @endproduction ``` 或者你也可以使用 @env 指令來判斷應用是否運行於特定的環境: ``` @env('staging') // The application is running in "staging"... @endenv @env(['staging', 'production']) // The application is running in "staging" or "production"... @endenv ``` #### 區塊指令 你可以使用 @hasSection 指令來判斷區塊是否有內容: ``` @hasSection('navigation') <div class="pull-right"> @yield('navigation') </div> <div class="clearfix"></div> @endif ``` 您也可以使用 sectionMissing 指令來判斷區塊是否沒有內容: ``` @sectionMissing('navigation') <div class="pull-right"> @include('default-navigation') </div> @endif ``` ### Switch 語句 你可使用 @switch , @case , @break , @default 和 @endswitch 語句來組出 Switch 語句: ``` @switch($i) @case(1) First case... @break @case(2) Second case... @break @default Default case... @endswitch ``` ### 迴圈 除了條件語句, 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 ``` > 技巧: > > 運作迴圈時,您可以使用迴圈變數 $loop 去取得有關迴圈的相關資訊。例如,您處於迴圈的第一個迭代又或是處於最後一個迭代 當在使用迴圈的時候,你可以透過 @continue 和 @break 來終止迴圈或跳過當前迭代: ``` @foreach ($users as $user) @if ($user->type == 1) @continue @endif <li>{{ $user->name }}</li> @if ($user->number == 5) @break @endif @endforeach ``` 你也可以在 @continue 和 @break 指令的後面加入條件 ``` @foreach ($users as $user) @continue($user->type == 1) <li>{{ $user->name }}</li> @break($user->number == 5) @endforeach ``` ### Loop 變數 迴圈時,迴圈內部可以使用 $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 ``` 如果你在多層迴圈中,可以使用迴圈的 $loop 的變數的 parent 屬性來訪問父級迴圈: ``` @foreach ($users as $user) @foreach ($user->posts as $post) @if ($loop->parent->first) This is 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| 當在多層迴圈中可取得上一層的父迴圈| ### 註解 Blade 也允許你在視圖中定義註解。但是和 HTML 註解不同, Blade 註解將不會被包含在應用回傳到前台的 HTML 中: `{{-- This comment will not be present in the rendered HTML --}}` ### 引入子視圖 Blade 的 @include 指令可用於從另一個視圖包含一個 Blade 子視圖,子視圖將繼承父視圖中所有可用的參數: ``` <div> @include('shared.errors') <form> <!-- Form Contents --> </form> </div> ``` 除了子視圖繼承父視圖中所有可用的參數,你亦可通過陣列將資料傳遞給子視圖 `@include('view.name', ['status' => 'complete'])` 如果您嘗試 @include 一個不存在的視圖,Laravel 將會報錯。如果您想要包含一個不確定是否存在的視圖時,您可以使用 @includeIf 指令: `@includeIf('view.name', ['status' => 'complete'])` 如果想要在某個表達式的值計算為 true 時 @include 一個視圖,您可以使用 @includeWhen 指令: 如果想要在某個表達式的值計算為 false 時 @include 一個視圖,您可以使用 @includeUnless 指令: ``` @includeWhen($boolean, 'view.name', ['status' => 'complete']) @includeUnless($boolean, 'view.name', ['status' => 'complete']) ``` 要包含指定的視圖陣列中存在的第一個視圖,可以使用 includeFirst 指令: `@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])` > 注意: > > 你應該避免在 Blade 視圖中使用 __DIR__ 和 __FILE__ 常數,因為它們會引用本地已經緩存了的或編譯過了的視圖 ### @once 指令 @once 指令允許你定義模板的一部分內容,這部分內容在每一個渲染周期中只會被處理一次。該指令在使用堆棧推送一段特定的 JavaScript 程式碼到頁面的頭部環境下是很有用的。例如如果想要在循環中渲染一個特定的組件,你可能希望僅在組件渲染的首次推送 JavaScript 程式碼到頭部: ``` @once @push('scripts') <script> // Your custom JavaScript... </script> @endpush @endonce ``` ### 原生 PHP 程式碼 在許多情況下,嵌入 PHP 程式碼到視圖中是很有用的。你可以在模板中使用 Blade 的 @php 指令執行原生的 PHP 程式碼: ``` @php $counter = 1; @endphp ``` ## 組件 組件和插槽的作用與區塊.布局以及 includes 的作用一致;不過,有些人可能覺着組件和插槽更易於理解。有兩種撰寫組件的方法:基於類別的組件以及匿名組件 你可以使用 make:component 這樣的 Artisan 命令來創建一個基於類別的組件。我們將會創建一個簡單的 Alert 組件用於說明如何使用組件。make:component 命令將會把組件置於 App\View\Components 資料夾中: `php artisan make:component Alert` make:component 命令將會為組件創建一個視圖模板。創建的視圖被置於 resources/views/components 資料夾中。當你為應用撰寫組件,位於 app/View/Components 以及 resources/views/components 資料夾的組件將自動地被讀入而無須自行註冊 你也可以在子資料夾內去建立組件 `php artisan make:component Forms/Input` 上面的指令將會建立 Input 組件在 App\View\Components\Forms 資料夾,而視圖將會被放在 resources/views/components/forms 資料夾 ### 手動註冊套件組件 當為你自己的應用編寫組件的時候,Laravel 將會自動發現位於 app/View/Components 資料夾和 resources/views/components 資料夾中的組件 當然,如果你使用 Blade 組件來建立一個套件,你可能需要手動註冊組件類別及其 HTML 標籤別名。你應該在套件的服務供應器的 boot() 中註冊組件: ``` use Illuminate\Support\Facades\Blade; /** * Bootstrap your package's services. */ public function boot() { Blade::component('package-alert', Alert::class); } ``` 當組件註冊完成後,便可使用標籤別名來對其進行渲染 `<x-package-alert/>` 作為替代,你能使用 componentNamespace() 來自動載入組件類別。例如, Nightsade 套件有 Calendar 和 ColorPicker 組件在 Package\Views\Components 命名空間裏頭 ``` use Illuminate\Support\Facades\Blade; /** * Bootstrap your package's services. * * @return void */ public function boot() { Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); } ``` 這樣能使用套件名稱接::語法的方式來使用套件組件 ``` <x-nightshade::calendar /> <x-nightshade::color-picker /> ``` Blade 將自動偵測鏈結到組件的類別透過小駝峰的方式來判讀組件名稱。同樣可以使用點語法來指定子資料夾 ### 渲染組件 要顯示一個組件,您可以在 Blade 模板中使用 Blade 組件標籤。 Blade 組件標籤以 x- 字串開始,其後緊接組件類別 kebab case 形式的名稱,即單詞與單詞之間使用短橫線 - 進行連接 ``` <x-alert/> <x-user-profile/> ``` 如果組件類別位於 App\View\Components 資料夾的子目錄中,您可以使用點語法來指定目錄層級。例如,假設我們有一個組件位於 App\View\Components\Inputs\Button.php,那麼我們可以像這樣渲染它: `<x-inputs.button/>` ### 傳資料到組件 你可以使用 HTML 屬性傳遞資料到 Blade 組件中。普通的值可以通過簡單的 HTML 屬性來傳入組件。至於 PHP 表達式和參數應該通過以 : 字元作為前綴的屬性來進行傳入: `<x-alert type="error" :message="$message"/>` 你應該在類別的建構子中去定義組件的必要資料。在組件的視圖中,組件的所有 public 類型的屬性都是可使用的。不必再通過組件類別的 render() 傳遞: ``` <?php namespace App\View\Components; use Illuminate\View\Component; class Alert extends Component { /** * The alert type. * * @var string */ public $type; /** * The alert message. * * @var string */ public $message; /** * Create the component instance. * * @param string $type * @param string $message * @return void */ public function __construct($type, $message) { $this->type = $type; $this->message = $message; } /** * Get the view / contents that represent the component. * * @return \Illuminate\View\View|\Closure|string */ public function render() { return view('components.alert'); } } ``` 當你的組件被渲染時,您可以透過用兩個大括號包入變數 來顯示組件的 public 屬性的內容 ``` <div class="alert alert-{{ $type }}"> {{ $message }} </div> ``` ### Casing 命名方式 組件的建構子參數應該使用小駝峰式命名,在 HTML 屬性中引用參數名時應該使用烤串式命名(kebab-case :單詞與單詞之間使用短橫線 - 進行連接)。例如組件建構子如下: ``` /** * Create the component instance. * * @param string $alertType * @return void */ public function __construct($alertType) { $this->alertType = $alertType; } ``` $alertType 參數將會對應組件屬性 alert-type `<x-alert alert-type="danger" />` ### 組件方法 除了在組件模板中可以使用 public 類型的變數以外,任何 public 類型的方法亦可在模板中執行。例如假設某組件擁有 isSelected() ``` /** * Determine if the given option is the current selected option. * * @param string $option * @return bool */ public function isSelected($option) { return $option === $this->selected; } ``` 你可以通過呼叫與方法名稱相同的變數名稱(也就是名稱前面加上$)來執行這個方法 ``` <option {{ $isSelected($value) ? 'selected="selected"' : '' }} value="{{ $value }}"> {{ $label }} </option> ``` #### 在類別內取用屬性 & 插槽 Blade 組件也允許你在類別的渲染方法中訪問組件的名稱,屬性以及插槽。當然,為了訪問這個資料,你應該在組件的 render 方法中返回一個 Closure。這個 Closure 接收一個名為 $data 的陣列作為它唯一的參數: ``` /** * Get the view / contents that represent the component. * * @return \Illuminate\View\View|\Closure|string */ public function render() { return function (array $data) { // $data['componentName']; // $data['attributes']; // $data['slot']; return '<div>Components content</div>'; }; } ``` componentName 等於使用 x- 作為前綴後 HTML 標籤中使用的名稱。 attributes 元素包含所有可能出現在 HTML 標籤中的屬性。 slot 元素是一個 Illuminate\Support\HtmlString 實例,該實例包含組件中的插槽定義的內容 該 Closure 需要回傳一個字串。假如該回傳字串與某個已存在的視圖有關,該視圖將會被渲染;否則該回傳字串將會被作為子視圖而嵌入 #### 額外依賴 如果你的組件需要使用服務容器中的依賴,可以在組件所有屬性前將其列出,服務容器就會自動將其注入: ``` use App\Services\AlertCreator /** * Create the component instance. * * @param \App\Services\AlertCreator $creator * @param string $type * @param string $message * @return void */ public function __construct(AlertCreator $creator, $type, $message) { $this->creator = $creator; $this->type = $type; $this->message = $message; } ``` ### 組件屬性 先前已經說過如何通過屬性傳遞資料給組件;然而,有時您可能需要指定一個諸如 class 的額外的 HTML 屬性,這些屬性不是組件所需要的。此時,您可能想要將這些額外的屬性向下傳遞到組件模板的根元素中。例如我們想要像這樣渲染一個 alert 組件: `<x-alert type="error" :message="$message" class="mt-4"/>` 所有不屬於組件建構子的屬性都將被自動添加到組件的「屬性包」中。該屬性包將通過 $attributes 變數自動傳遞給組件。您可以通過用兩個大括號來包住 $attributes 來渲染所有的屬性: ``` <div {{ $attributes }}> <!-- Component content --> </div> ``` 你不能用在組件標籤內使用 Blade 標籤。例如: <x-alert :live="@env('production')"/>將不會有作用 #### 預設 / 合併 屬性 某些時候,你可能需要指定屬性的預設值,或將其他值合併到組件的某些屬性中。為此,你可以使用屬性包的 merge() , 這在需要為某個組件固定加入一些 CSS 類別時特別好用 ``` <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 屬性的合併 如果要合併的屬性不是 class 屬性的話,那傳入 merge() 的值將被視為屬性的預設值。然而與 class 屬性不同,這些屬性將不會與注入屬性值進行合併。相對的,它們將會被複寫。例如一個按鈕組件的實作會像這樣: ``` <button {{ $attributes->merge(['type' => 'button']) }}> {{ $slot }} </button> ``` 為了要以指定類型來渲染該按鈕組件,就需要傳入指定類型。假如沒有指定任何類型,按鈕類型將會使用預設值 button ,而如果有傳入指定類型,則使用傳入的類型 ``` <x-button type="submit"> Submit </x-button> ``` 渲染之後的按鈕組件的 HTML 結構就是這樣 ``` <button type="submit"> Submit </button> ``` 假如你想要有不是 class 的屬性也能有和它相同的處理方式,也就是將傳入之值與預設值合併在一起,你能使用 prepends() 。在這個例子中, data-controller 屬性將永遠以 profile-controller 作為前綴,而任何額外傳入的 data-controller 的值將連接到後面 ``` <div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}> {{ $slot }} </div> ``` #### 取用 & 過濾 屬性 你可以使用 filter() 過濾屬性。如果你想在屬性包中保留屬性,應在filter() 傳入一個屬性包應要保留的屬性時回傳 true 的 Closure,看下面例子會比較好理解: `{{ $attributes->filter(fn ($value, $key) => $key == 'foo') }}` 為了方便,你可以使用 whereStartsWith() 來取回所有由指定字串開頭的屬性 `{{ $attributes->whereStartsWith('wire:model') }}` 使用 first() ,你可以渲染 attribute 包的第一個屬性 `{{ $attributes->whereStartsWith('wire:model')->first() }}` 假如你想要檢查組件裡是否有某個屬性,你可以使用 has()。這個方法接受一個屬性名稱作為唯一參數並根據該屬性是否存在來回傳 Boolean ``` @if ($attributes->has('class')) <div>Class attribute is present</div> @endif ``` 最後,你可以透過 get() 來取得特定屬性的值 `{{ $attributes->get('class') }}` ### 插槽 通常你需要通過插槽向組件傳遞額外內容,組件插槽將會透過 {{ $slot }} 來渲染。 為了更好的說明這個觀念,假設我們創建的 alert 組件具有以下標記: ``` <!-- /resources/views/components/alert.blade.php --> <div class="alert alert-danger"> {{ $slot }} </div> ``` 我們可以透過注入內容到組件來傳遞內容到插槽 ``` <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 name="title"> Server Error </x-slot> <strong>Whoops!</strong> Something went wrong! </x-alert> ``` #### 插槽作用域 如果您使用諸如 Vue 這樣的 JavaScript 框架,那你應該很熟悉「作用域插槽」,它允許你從插槽中的組件訪問資料或者方法。 Laravel 中也有類似的用法,只需在你的組件中定義 public 方法或屬性,並且使用 $component 變數來訪問插槽中的組件: ``` <x-alert> <x-slot name="title"> {{ $component->formatAlert('Server Error') }} </x-slot> <strong>Whoops!</strong> Something went wrong! </x-alert> ``` ### 內嵌組件視圖 對於小型組件而言,管理組件類和組件視圖模板可能會很麻煩。因此你可以從 render() 方法中直接回傳組件的內容 ``` /** * Get the view / contents that represent the component. * * @return \Illuminate\View\View|\Closure|string */ public function render() { return <<<'blade' <div class="alert alert-danger"> {{ $slot }} </div> blade; } ``` #### 生成內嵌組件視圖 要創建一個渲染內嵌視圖的組件,您可以在運行 make:component 命令時使用 inline 選項: `php artisan make:component Alert --inline` ### 匿名組件 與內嵌組件相同,匿名組件提供了一個通過單個檔案管理組件的機制。然而,匿名組件使用的是一個沒有關聯類別的單一視圖檔案。要定義一個匿名組件,您只需將 Blade 模板置於 resources/views/components 資料夾下。例如假設你在 resources/views/components/alert.blade.php 中定義了一個組件,你可以這樣去渲染它 `<x-alert/>` 你能夠使用點語法去說明該組件位在 components 資料夾的子資料夾。例如假設組件定義於 resources/views/components/inputs/button.blade.php,你必須這樣去進行渲染 `<x-inputs.button/>` #### 資料屬性 由於匿名組件沒有任何關聯類別,你可能想要區分哪些資料應該被作為變數傳遞給組件,而哪些屬性應該被存放於屬性包中 你可以在組件的 Blade 模板的頂層使用 @props 指令來指定哪些屬性應該作為資料變數。組件中的其他屬性都將通過屬性包的形式提供。如果你想要為某個資料變數指定一個預設值,你可以將屬性名作為陣列的key,預設值作為陣列的值來實現 ``` <!-- /resources/views/components/alert.blade.php --> @props(['type' => 'info', 'message']) <div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}> {{ $message }} </div> ``` 根據上面定義的組件,我們能這樣去渲染組件 `<x-alert type="error" :message="$message" class="mb-4"/>` ### 動態組件 有時你可能需要渲染一個組件,但在運行前不知道要渲染哪一個。在這種情況下你可以使用 Laravel 內建的動態組件來渲染一個基於值或變數的組件: `<x-dynamic-component :component="$componentName" class="mt-4" />` ## 建立布局 ### 使用組件來佈局 大部分的網頁應用的多個頁面都會包含很多同樣的布局,如果透過複製貼上程式碼的方式來生成這些布局的話將會非常不好管理。幸好你可以把這些重複布局轉成單一 Blade 組件然後重複的在應用裏頭使用它 #### 定義布局組件 例如,想像我們要建立一個工作清單應用,我們能定義一個布局組件,像這個樣子: ``` <!-- resources/views/components/layout.blade.php --> <html> <head> <title>{{ $title ?? 'Todo Manager' }}</title> </head> <body> <h1>Todos</h1> <hr/> {{ $slot }} </body> </html> ``` #### 採用布局組件 一旦布局組件被定義了,我們就能建立應用該組件的 Blade 視圖。在這個例子中,我們將定義一個簡單的視圖用來顯示我們的工作清單 ``` <!-- resources/views/tasks.blade.php --> <x-layout> @foreach ($tasks as $task) {{ $task }} @endforeach </x-layout> ``` 看不太懂,先不翻 Remember, content that is injected into a component will be supplied to the default $slot variable within our layout component. As you may have noticed, our layout also respects a $title slot if one is provided; otherwise, a default title is shown. We may inject a custom title from our task list view using the standard slot syntax discussed in the component documentation: ``` <!-- resources/views/tasks.blade.php --> <x-layout> <x-slot name="title"> Custom Title </x-slot> @foreach ($tasks as $task) {{ $task }} @endforeach </x-layout> ``` Now that we have defined our layout and task list views, we just need to return the task view from a route: ``` use App\Models\Task; Route::get('/tasks', function () { return view('tasks', ['tasks' => Task::all()]); }); ``` ### 布局使用版型繼承 #### 定義布局 布局也透過版型繼承來建立。這是最主要用來建立應用的方法 現在來看一個簡單的例子。首先我們檢查一個頁面布局,因為應用裡的大部分頁面包含相同的布局,如能把這些相同的布局作為單一 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> ``` 如你所見,這個檔案包含 HTML 結構。然後請注意 @section 和 @yield 指令。 @section 指令如其名所暗示,定義一個區塊的內容,我稱之為"填洞"。而 @yield 指令則是用來指定某個位置要用來顯示某區塊的內容,這概念我稱之為"挖洞" 現在我們為應用定義一個版型,現在定義一個子頁面用來繼承該版型 #### 繼承一個版型 當定義一個子視圖,使用 @extends Blade 指令來說明這個視圖要繼承哪一個父視圖。接著子視圖就可以透過 @section 指令來定義指定 @yield 指令所在位置所要填入的內容。 > 懶人包 > > 父視圖會包含整個網頁的大部分結構以及用 @yield 來挖洞,子視圖宣告要繼承哪個父視圖以及用 @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 ``` 在這個例子中, sidebar 區塊有包含 @parent 指令去擴充而不是覆蓋 sidebar 布局的內容。 @parent 指令將會替換成原先父視圖同名區塊的內容。可理解為子視圖不但要填父視圖的洞還會取用父視圖原本的內容 相對於之前的例子,sidebar 區塊的結尾改成 @show 而非原本的 @endsection。@endsection 是在定義填洞的內容,而 @show 則是在定義內容之餘同時挖洞 @yield 指令也接受一個預設值作為第二參數,當這個區塊並沒被填的時候,這個預設值就會被使用 `@yield('content', 'Default content')` ## 表單 ### CSRF 欄位 任何你在應用中定義 HTML 表單的時候,您都應該在表單中包含一個隱藏的 CSRF token。這樣一來, CSRF 保護中介層便能驗證請求。你可以使用 @csrf Blade 指令來生成一個 token ``` <form method="POST" action="/profile"> @csrf ... </form> ``` ### 方法欄位 由於 HTML 表單不能夠生成 PUT , PATCH 或 DELETE 請求,所以你需要添加隱藏的 _method 輸入項來模擬這些 HTTP 動作。你亦可使用 @method Blade 指令來創建這個方法 ``` <form action="/foo/bar" method="POST"> @method('PUT') ... </form> ``` ### 驗證錯誤 @error 指令可用於快速檢查指定屬性是否存在驗證錯誤資訊。在 @error 指令裏頭,您可以透過{{ $message }} 來顯示錯誤內容 ``` //resources/views/post/create.blade.php <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 ``` 你可以將指定錯誤包的名稱作為 @error 指令的第二個參數來確認是否含有多個表單的頁面的驗證錯誤資料: ``` //resources/views/auth.blade.php <label for="email">Email address</label> <input id="email" type="email" class="@error('email', 'login') is-invalid @enderror"> @error('email', 'login') <div class="alert alert-danger">{{ $message }}</div> @enderror ``` ## 堆疊 Blade 允許你將視圖推送到堆疊中,它們可以在其他視圖或布局中進行渲染,這在子視圖中需要指定 JavaScript 函式庫時非常有用 ``` @push('scripts') <script src="/example.js"></script> @endpush ``` 你也可按需進行多次推送,請透過 @stack 指令來完整渲染堆疊的內容 ``` <head> <!-- Head Contents --> @stack('scripts') </head> ``` 如果你想要預置內容於堆疊的頂部,可以使用 @prepend 指令 ``` @push('scripts') This will be second... @endpush // Later... @prepend('scripts') This will be first... @endprepend ``` ## 服務注入 @inject 指令可用於從 Laravel 的服務容器中取得服務。傳遞給 @inject 的第一個參數是將要置入的服務變數名,第二個參數可以是你想要解析的類別完整名或接口完整名 ``` @inject('metrics', 'App\Services\MetricsService') <div> Monthly Revenue: {{ $metrics->monthlyRevenue() }}. </div> ``` ## 擴展指令 Blade 允許你使用 directive() 來自定義指令。當 Blade 編譯器遇到自定義指令時,將會呼叫其包含的表達式提供的回呼方法 下方的例子創建了一個 @datetime($var) 指令,其用于格式化指定的 DateTime 實例 $var: ``` <?php namespace App\Providers; use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { // } /** * Bootstrap any application services. * * @return void */ public function boot() { Blade::directive('datetime', function ($expression) { return "<?php echo ($expression)->format('m/d/Y H:i'); ?>"; }); } } ``` 如您所見,我們將在任何被傳遞給該指令的表達式中接著呼叫 format()。因此在這個例子中,最終該指令生成的 PHP 程式碼如下: `<?php echo ($var)->format('m/d/Y H:i'); ?>` > 注意: > > 在更新了 Blade 指令的邏輯後,您需要刪除所有的 Blade 視圖緩存才能使之生效。可通過 view:clear 這個 Artisan 命令來實現 ### 自定義 if 語句 在定義一個簡單的自定義的條件語句時,編寫自定義指令可能會比必要的步驟還要複雜。因此, Blade 提供了一個 Blade::if(),它允許你使用 Closure 快速自定義一個條件指令。例如讓我們自定義一個檢查當前應用的雲服務商的條件指令。我們可以在 AppServiceProvider 中的 boot() 中這樣做: ``` use Illuminate\Support\Facades\Blade; /** * Bootstrap any application services. * * @return void */ public function boot() { Blade::if('disk', function ($value) { return config('filesystems.default') === $value; }); } ``` 當自定義條件語句完成定義後,我們可以在模板中方便地使用它們 ``` @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 ```