跳至内容

Blade 模板

简介

Blade 是 Laravel 自带的简单而强大的模板引擎。 与一些 PHP 模板引擎不同,Blade 不限制您在模板中使用纯 PHP 代码。 事实上,所有 Blade 模板都会被编译成纯 PHP 代码并缓存,直到它们被修改,这意味着 Blade 对您的应用程序基本上没有增加任何开销。 Blade 模板文件使用 .blade.php 文件扩展名,通常存储在 resources/views 目录中。

可以使用全局 view 辅助函数从路由或控制器返回 Blade 视图。 当然,正如 视图 文档中所述,可以使用 view 辅助函数的第二个参数将数据传递给 Blade 视图

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

使用 Livewire 增强 Blade

想让您的 Blade 模板更上一层楼并轻松构建动态界面吗? 看看 Laravel Livewire。 Livewire 允许您编写 Blade 组件,这些组件通过动态功能增强,这些功能通常只能通过 React 或 Vue 等前端框架实现,从而提供了一种构建现代、响应式前端的好方法,而无需许多 JavaScript 框架的复杂性、客户端渲染或构建步骤。

显示数据

您可以通过将变量包装在花括号中来显示传递给 Blade 视图的数据。 例如,给定以下路由

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

您可以像这样显示 name 变量的内容

Hello, {{ $name }}.
lightbulb

Blade 的 {{ }} 回显语句会自动通过 PHP 的 htmlspecialchars 函数发送,以防止 XSS 攻击。

您不仅限于显示传递给视图的变量的内容。 您还可以回显任何 PHP 函数的结果。 实际上,您可以在 Blade 回显语句中放入任何您想要的 PHP 代码

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

HTML 实体编码

默认情况下,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();
}
}

显示未转义的数据

默认情况下,Blade 的 {{ }} 语句会自动通过 PHP 的 htmlspecialchars 函数发送,以防止 XSS 攻击。 如果您不希望您的数据被转义,您可以使用以下语法

Hello, {!! $name !!}.
exclamation

当回显由您的应用程序用户提供的内容时,请务必小心。 您通常应使用转义的双花括号语法来防止在显示用户提供的数据时发生 XSS 攻击。

Blade 和 JavaScript 框架

由于许多 JavaScript 框架也使用“花括号”来表示应该在浏览器中显示给定的表达式,因此您可以使用 @ 符号通知 Blade 渲染引擎表达式应该保持不变。 例如

<h1>Laravel</h1>
 
Hello, @{{ name }}.

在此示例中,@ 符号将被 Blade 删除; 但是,{{ name }} 表达式将保持 Blade 引擎不变,从而允许它由您的 JavaScript 框架呈现。

@ 符号也可用于转义 Blade 指令

{{-- Blade template --}}
@@if()
 
<!-- HTML output -->
@if()

渲染 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 语句,该语句将给定的对象或数组转换为有效的 JavaScript 对象

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

最新版本的 Laravel 应用程序框架包含一个 Js 外观模式,它提供了在您的 Blade 模板中方便地访问此功能的方法

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

您应该仅使用 Js::from 方法将现有变量渲染为 JSON。 Blade 模板基于正则表达式,尝试将复杂表达式传递给指令可能会导致意外的失败。

@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 指令时应检查的身份验证守卫

@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

会话指令

@session 指令可用于确定是否存在 会话 值。 如果会话值存在,则将评估 @session@endsession 指令中的模板内容。 在 @session 指令的内容中,您可以回显 $value 变量以显示会话值

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

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
lightbulb

在迭代 foreach 循环时,您可以使用循环变量来获取有关循环的有用信息,例如您是否处于循环的第一次或最后一次迭代。

使用循环时,您还可以使用 @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

循环变量

在迭代 foreach 循环时,循环内部将可以使用 $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 当在嵌套循环中时,父循环的变量。

条件类和样式

@class 指令有条件地编译 CSS 类字符串。该指令接受一个类数组,其中数组键包含您希望添加的类或多个类,而值是一个布尔表达式。如果数组元素具有数字键,则它将始终包含在呈现的类列表中

@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 指令可用于有条件地向 HTML 元素添加内联 CSS 样式

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

附加属性

为方便起见,您可以使用 @checked 指令轻松指示给定的 HTML 复选框输入是否为“选中”状态。如果提供的条件评估为 true,则此指令将回显 checked

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

同样,@selected 指令可用于指示给定的选择选项是否应为“选中”状态

<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>

此外,可以使用 @disabled 指令来指示是否应将给定的元素设置为“禁用”

<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>

此外,可以使用 @readonly 指令来指示是否应将给定的元素设置为“只读”

<input
type="email"
name="email"
@readonly($user->isNotAdmin())
/>

此外,可以使用 @required 指令来指示是否应将给定的元素设置为“必填”

<input
type="text"
name="title"
value="title"
@required($user->isAdmin())
/>

包含子视图

lightbulb

虽然您可以自由使用 @include 指令,但 Blade 组件提供了类似的功能,并且与 @include 指令相比,具有一些优势,例如数据和属性绑定。

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'])

如果您想在给定的布尔表达式评估为 truefalse@include 一个视图,则可以使用 @includeWhen@includeUnless 指令

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

要从给定的视图数组中包含第一个存在的视图,可以使用 includeFirst 指令

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

您应该避免在 Blade 视图中使用 __DIR____FILE__ 常量,因为它们将引用缓存的已编译视图的位置。

为集合渲染视图

您可以使用 Blade 的 @each 指令将循环和包含组合成一行

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

@each 指令的第一个参数是要为数组或集合中的每个元素呈现的视图。第二个参数是要迭代的数组或集合,而第三个参数是将在视图中分配给当前迭代的变量名称。因此,例如,如果您正在迭代一个 jobs 数组,通常您希望在视图中将每个作业作为 job 变量访问。当前迭代的数组键将在视图中作为 key 变量可用。

您还可以将第四个参数传递给 @each 指令。此参数确定如果给定的数组为空时将呈现的视图。

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

通过 @each 呈现的视图不会继承父视图中的变量。如果子视图需要这些变量,则应改为使用 @foreach@include 指令。

@once 指令

@once 指令允许您定义模板的一部分,该部分在每个呈现周期中仅评估一次。这对于使用堆栈将给定的 JavaScript 推送到页面的标头可能很有用。例如,如果您在循环中呈现给定的 组件,您可能希望仅在第一次呈现组件时才将 JavaScript 推送到标头

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

由于 @once 指令通常与 @push@prepend 指令结合使用,因此为了方便起见,提供了 @pushOnce@prependOnce 指令

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

原始 PHP

在某些情况下,将 PHP 代码嵌入视图中很有用。您可以使用 Blade @php 指令在模板中执行一段纯 PHP 代码

@php
$counter = 1;
@endphp

或者,如果您只需要使用 PHP 导入类,则可以使用 @use 指令

@use('App\Models\Flight')

可以为 @use 指令提供第二个参数来为导入的类设置别名

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

注释

Blade 还允许您在视图中定义注释。但是,与 HTML 注释不同,Blade 注释不包含在您的应用程序返回的 HTML 中

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

组件

组件和插槽提供了与节、布局和包含类似的好处;但是,有些人可能会发现组件和插槽的心理模型更容易理解。编写组件有两种方法:基于类的组件和匿名组件。

要创建基于类的组件,您可以使用 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

上面的命令将在 app/View/Components/Forms 目录中创建一个 Input 组件,并且该视图将放置在 resources/views/components/forms 目录中。

如果要创建匿名组件(仅具有 Blade 模板而没有类的组件),则可以在调用 make:component 命令时使用 --view 标志

php artisan make:component forms.input --view

上面的命令将在 resources/views/components/forms/input.blade.php 中创建一个 Blade 文件,该文件可以通过 <x-forms.input /> 作为组件呈现。

手动注册包组件

当为自己的应用程序编写组件时,会自动在 app/View/Components 目录和 resources/views/components 目录中发现组件。

但是,如果您正在构建一个利用 Blade 组件的包,则需要手动注册您的组件类及其 HTML 标签别名。您通常应该在包的服务提供程序的 boot 方法中注册您的组件

use Illuminate\Support\Facades\Blade;
 
/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::component('package-alert', Alert::class);
}

注册组件后,可以使用其标签别名呈现它

<x-package-alert/>

或者,您可以使用 componentNamespace 方法按照约定自动加载组件类。例如,Nightshade 包可能具有 CalendarColorPicker 组件,它们位于 Package\Views\Components 命名空间中

use Illuminate\Support\Facades\Blade;
 
/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

这将允许使用 package-name:: 语法通过其供应商命名空间使用包组件

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

Blade 将通过将组件名称转换为帕斯卡命名法来自动检测链接到此组件的类。还支持使用“点”表示法的子目录。

渲染组件

要显示组件,您可以在 Blade 模板中使用 Blade 组件标签。Blade 组件标签以字符串 x- 开头,后跟组件类的烤肉串式名称

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

如果组件类嵌套在 app/View/Components 目录中更深的位置,则可以使用 . 字符来指示目录嵌套。例如,如果我们假设组件位于 app/View/Components/Inputs/Button.php,我们可以像这样呈现它

<x-inputs.button/>

如果要有条件地呈现组件,可以在组件类上定义 shouldRender 方法。如果 shouldRender 方法返回 false,则不会呈现该组件

use Illuminate\Support\Str;
 
/**
* Whether the component should be rendered
*/
public function shouldRender(): bool
{
return Str::length($this->message) > 0;
}

索引组件

有时,组件是组件组的一部分,您可能希望将相关组件分组在一个目录中。例如,想象一下具有以下类结构的“卡片”组件

App\Views\Components\Card\Card
App\Views\Components\Card\Header
App\Views\Components\Card\Body

由于根 Card 组件嵌套在 Card 目录中,您可能会期望您需要通过 <x-card.card> 呈现该组件。但是,当组件的文件名与组件的目录名称匹配时,Laravel 会自动假设该组件是“根”组件,并允许您在不重复目录名称的情况下呈现该组件

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

向组件传递数据

您可以使用 HTML 属性将数据传递给 Blade 组件。可以使用简单的 HTML 属性字符串将硬编码的原始值传递给组件。应通过使用 : 字符作为前缀的属性将 PHP 表达式和变量传递给组件

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

您应该在其类的构造函数中定义组件的所有数据属性。组件上的所有公共属性都将自动提供给组件的视图。没有必要从组件的 render 方法将数据传递给视图

<?php
 
namespace App\View\Components;
 
use Illuminate\View\Component;
use Illuminate\View\View;
 
class Alert extends Component
{
/**
* Create the component instance.
*/
public function __construct(
public string $type,
public string $message,
) {}
 
/**
* Get the view / contents that represent the component.
*/
public function render(): View
{
return view('components.alert');
}
}

呈现组件时,您可以通过回显名称来显示组件的公共变量的内容

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

大小写

组件构造函数参数应使用 camelCase 指定,而在 HTML 属性中引用参数名称时应使用 kebab-case。例如,给定以下组件构造函数

/**
* Create the component instance.
*/
public function __construct(
public string $alertType,
) {}

$alertType 参数可以像这样提供给组件

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

短属性语法

在向组件传递属性时,您也可以使用“短属性”语法。这通常很方便,因为属性名称经常与它们对应的变量名称匹配。

{{-- Short attribute syntax... --}}
<x-profile :$userId :$name />
 
{{-- Is equivalent to... --}}
<x-profile :user-id="$userId" :name="$name" />

转义属性渲染

由于某些 JavaScript 框架(如 Alpine.js)也使用冒号前缀的属性,您可以使用双冒号(::)前缀来告知 Blade 该属性不是 PHP 表达式。例如,给定以下组件:

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

以下 HTML 将由 Blade 渲染:

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

组件方法

除了组件模板中可用的公共变量之外,还可以调用组件上的任何公共方法。例如,假设一个组件具有 isSelected 方法:

/**
* Determine if the given option is the currently selected option.
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}

您可以通过调用与方法名称匹配的变量,从组件模板中执行此方法:

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

在组件类中访问属性和插槽

Blade 组件还允许您在类的 render 方法中访问组件名称、属性和插槽。但是,为了访问这些数据,您应该从组件的 render 方法返回一个闭包:

use Closure;
 
/**
* Get the view / contents that represent the component.
*/
public function render(): Closure
{
return function () {
return '<div {{ $attributes }}>Components content</div>';
};
}

组件的 render 方法返回的闭包也可以接收一个 $data 数组作为其唯一参数。此数组将包含一些元素,这些元素提供有关组件的信息:

return function (array $data) {
// $data['componentName'];
// $data['attributes'];
// $data['slot'];
 
return '<div {{ $attributes }}>Components content</div>';
}
exclamation

$data 数组中的元素绝不应直接嵌入到 render 方法返回的 Blade 字符串中,因为这样做可能会通过恶意属性内容允许远程代码执行。

componentName 等于 HTML 标签中 x- 前缀之后使用的名称。因此 <x-alert />componentName 将是 alertattributes 元素将包含 HTML 标签上的所有属性。slot 元素是 Illuminate\Support\HtmlString 实例,其中包含组件插槽的内容。

闭包应返回一个字符串。如果返回的字符串对应于现有视图,则将呈现该视图;否则,返回的字符串将被评估为内联 Blade 视图。

其他依赖项

如果您的组件需要来自 Laravel 服务容器 的依赖项,您可以在任何组件的数据属性之前列出它们,并且它们将由容器自动注入:

use App\Services\AlertCreator;
 
/**
* Create the component instance.
*/
public function __construct(
public AlertCreator $creator,
public string $type,
public string $message,
) {}

隐藏属性/方法

如果您想阻止某些公共方法或属性作为变量暴露给您的组件模板,您可以将它们添加到组件的 $except 数组属性中:

<?php
 
namespace App\View\Components;
 
use Illuminate\View\Component;
 
class Alert extends Component
{
/**
* The properties / methods that should not be exposed to the component template.
*
* @var array
*/
protected $except = ['type'];
 
/**
* Create the component instance.
*/
public function __construct(
public string $type,
) {}
}

组件属性

我们已经研究了如何将数据属性传递给组件;但是,有时您可能需要指定额外的 HTML 属性,例如 class,这些属性不是组件正常运行所需数据的一部分。通常,您希望将这些额外的属性传递给组件模板的根元素。例如,假设我们要像这样渲染一个 alert 组件:

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

不属于组件构造函数的所有属性都将自动添加到组件的“属性包”中。此属性包通过 $attributes 变量自动提供给组件。所有属性都可以通过回显此变量在组件中呈现:

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

目前不支持在组件标签中使用诸如 @env 之类的指令。例如,<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>

有条件地合并类

有时,您可能希望在给定条件为 true 时合并类。您可以通过 class 方法来实现这一点,该方法接受一个类数组,其中数组键包含您希望添加的类,而值是一个布尔表达式。如果数组元素具有数字键,它将始终包含在渲染的类列表中:

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

如果您需要将其他属性合并到您的组件中,您可以将 merge 方法链接到 class 方法:

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

如果您需要在不应接收合并属性的其他 HTML 元素上有条件地编译类,您可以使用 @class 指令

非类属性合并

合并不是 class 属性的属性时,提供给 merge 方法的值将被视为属性的“默认”值。但是,与 class 属性不同,这些属性不会与注入的属性值合并。相反,它们将被覆盖。例如,button 组件的实现可能如下所示:

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

要使用自定义 type 渲染按钮组件,可以在使用组件时指定它。如果未指定类型,将使用 button 类型:

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

此示例中 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 方法筛选属性。此方法接受一个闭包,如果您希望在属性包中保留该属性,则该闭包应返回 true

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

为方便起见,您可以使用 whereStartsWith 方法来检索其键以给定字符串开头的所有属性:

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

相反,可以使用 whereDoesntStartWith 方法排除其键以给定字符串开头的所有属性:

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

使用 first 方法,您可以呈现给定属性包中的第一个属性:

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

如果您想检查组件上是否存在某个属性,可以使用 has 方法。此方法接受属性名称作为其唯一参数,并返回一个布尔值,指示该属性是否存在:

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

如果将数组传递给 has 方法,该方法将确定组件上是否存在所有给定的属性:

@if ($attributes->has(['name', 'class']))
<div>All of the attributes are present</div>
@endif

可以使用 hasAny 方法来确定组件上是否存在任何给定的属性:

@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>One of the attributes is present</div>
@endif

您可以使用 get 方法检索特定属性的值:

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

保留关键字

默认情况下,某些关键字保留供 Blade 内部使用以渲染组件。以下关键字不能在您的组件中定义为公共属性或方法名称:

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

插槽

您通常需要通过“插槽”将其他内容传递给您的组件。组件插槽通过回显 $slot 变量来呈现。为了探索这个概念,让我们假设一个 alert 组件具有以下标记:

<!-- /resources/views/components/alert.blade.php -->
 
<div class="alert alert-danger">
{{ $slot }}
</div>

我们可以通过将内容注入到组件中来将内容传递给 slot

<x-alert>
<strong>Whoops!</strong> Something went wrong!
</x-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 方法来确定插槽是否包含内容:

<span class="alert-title">{{ $title }}</span>
 
<div class="alert alert-danger">
@if ($slot->isEmpty())
This is default content if the slot is empty.
@else
{{ $slot }}
@endif
</div>

此外,hasActualContent 方法可用于确定插槽是否包含任何不是 HTML 注释的“实际”内容:

@if ($slot->hasActualContent())
The scope has non-comment content.
@endif

作用域插槽

如果您使用过诸如 Vue 之类的 JavaScript 框架,您可能熟悉“作用域插槽”,它允许您从组件中访问插槽中的数据或方法。您可以通过在组件上定义公共方法或属性,并通过 $component 变量在插槽中访问组件,在 Laravel 中实现类似的行为。在此示例中,我们将假设 x-alert 组件在其组件类上定义了一个公共的 formatAlert 方法:

<x-alert>
<x-slot:title>
{{ $component->formatAlert('Server Error') }}
</x-slot>
 
<strong>Whoops!</strong> Something went wrong!
</x-alert>

插槽属性

与 Blade 组件一样,您可以将其他 属性(例如 CSS 类名称)分配给插槽:

<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>

内联组件视图

对于非常小的组件,同时管理组件类和组件的视图模板可能会感到很麻烦。因此,您可以直接从 render 方法返回组件的标记:

/**
* Get the view / contents that represent the component.
*/
public function render(): string
{
return <<<'blade'
<div class="alert alert-danger">
{{ $slot }}
</div>
blade;
}

生成内联视图组件

要创建渲染内联视图的组件,您可以在执行 make:component 命令时使用 inline 选项:

php artisan make:component Alert --inline

动态组件

有时您可能需要渲染一个组件,但直到运行时才知道应该渲染哪个组件。在这种情况下,您可以使用 Laravel 内置的 dynamic-component 组件根据运行时值或变量来渲染组件:

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

手动注册组件

exclamation

以下关于手动注册组件的文档主要适用于编写包含视图组件的 Laravel 包的开发人员。如果您不是在编写包,则组件文档的这一部分可能与您无关。

当为自己的应用程序编写组件时,会自动在 app/View/Components 目录和 resources/views/components 目录中发现组件。

但是,如果您正在构建一个利用 Blade 组件或将组件放置在非常规目录中的包,则需要手动注册您的组件类及其 HTML 标签别名,以便 Laravel 知道在哪里找到该组件。您通常应该在包的服务提供者的 boot 方法中注册您的组件:

use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;
 
/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::component('package-alert', AlertComponent::class);
}

注册组件后,可以使用其标签别名呈现它

<x-package-alert/>

自动加载包组件

或者,您可以使用 componentNamespace 方法按照约定自动加载组件类。例如,Nightshade 包可能具有 CalendarColorPicker 组件,它们位于 Package\Views\Components 命名空间中

use Illuminate\Support\Facades\Blade;
 
/**
* Bootstrap your package's services.
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

这将允许使用 package-name:: 语法通过其供应商命名空间使用包组件

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

Blade 将通过将组件名称转换为帕斯卡命名法来自动检测链接到此组件的类。还支持使用“点”表示法的子目录。

匿名组件

类似于内联组件,匿名组件提供了一种通过单个文件管理组件的机制。但是,匿名组件使用单个视图文件,并且没有关联的类。要定义一个匿名组件,您只需将 Blade 模板放置在您的 resources/views/components 目录中。例如,假设您在 resources/views/components/alert.blade.php 定义了一个组件,您可以像这样简单地渲染它

<x-alert/>

您可以使用 . 字符来指示组件是否嵌套在 components 目录的更深层级中。例如,假设组件定义在 resources/views/components/inputs/button.blade.php,您可以像这样渲染它

<x-inputs.button/>

匿名索引组件

有时,当一个组件由许多 Blade 模板组成时,您可能希望将给定组件的模板分组到一个目录中。例如,想象一个具有以下目录结构的“手风琴”组件

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

此目录结构允许您像这样渲染手风琴组件及其项

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

但是,为了通过 x-accordion 渲染手风琴组件,我们不得不将“index”手风琴组件模板放置在 resources/views/components 目录中,而不是将其与其他手风琴相关模板一起嵌套在 accordion 目录中。

值得庆幸的是,Blade 允许您在组件目录本身中放置一个与组件目录名称匹配的文件。当此模板存在时,即使它嵌套在目录中,也可以将其渲染为组件的“根”元素。因此,我们可以继续使用上面示例中给出的相同 Blade 语法;但是,我们将调整我们的目录结构,如下所示

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

数据属性 / 特性

由于匿名组件没有任何关联的类,您可能想知道如何区分哪些数据应作为变量传递给组件,哪些属性应放置在组件的属性包中。

您可以使用组件 Blade 模板顶部的 @props 指令指定哪些属性应被视为数据变量。组件上的所有其他属性都将通过组件的属性包提供。如果您希望为数据变量提供默认值,可以将变量的名称指定为数组键,将默认值指定为数组值

<!-- /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"/>

访问父数据

有时,您可能希望从子组件内部访问父组件中的数据。在这种情况下,您可以使用 @aware 指令。例如,想象一下我们正在构建一个复杂的菜单组件,它由一个父组件 <x-menu> 和一个子组件 <x-menu.item> 组成

<x-menu color="purple">
<x-menu.item>...</x-menu.item>
<x-menu.item>...</x-menu.item>
</x-menu>

<x-menu> 组件的实现可能如下所示

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

由于 color 属性仅传递给父组件 (<x-menu>),因此它在 <x-menu.item> 内部不可用。但是,如果我们使用 @aware 指令,我们也可以使其在 <x-menu.item> 内部可用

<!-- /resources/views/components/menu/item.blade.php -->
 
@aware(['color' => 'gray'])
 
<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
{{ $slot }}
</li>
exclamation

@aware 指令无法访问未通过 HTML 属性显式传递给父组件的父数据。未显式传递给父组件的默认 @props 值无法通过 @aware 指令访问。

匿名组件路径

如前所述,匿名组件通常通过将 Blade 模板放置在您的 resources/views/components 目录中来定义。但是,您有时可能希望向 Laravel 注册除默认路径之外的其他匿名组件路径。

anonymousComponentPath 方法接受匿名组件位置的“路径”作为其第一个参数,并接受组件应放置在其下的可选“命名空间”作为其第二个参数。通常,此方法应从您的应用程序的服务提供者之一的 boot 方法中调用

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::anonymousComponentPath(__DIR__.'/../components');
}

当组件路径在注册时没有指定前缀(如上面的示例中所示)时,它们也可以在您的 Blade 组件中渲染,而无需相应的前缀。例如,如果 panel.blade.php 组件存在于上面注册的路径中,则可以像这样渲染它

<x-panel />

可以将前缀“命名空间”作为 anonymousComponentPath 方法的第二个参数提供

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

当提供前缀时,可以通过在组件渲染时将组件的命名空间前缀到组件名称来渲染该“命名空间”内的组件

<x-dashboard::panel />

构建布局

使用组件的布局

大多数 Web 应用程序在各种页面上保持相同的通用布局。如果我们在创建的每个视图中都必须重复整个布局 HTML,那么维护我们的应用程序将非常麻烦且困难。值得庆幸的是,将此布局定义为单个 Blade 组件,然后在整个应用程序中使用它很方便。

定义布局组件

例如,假设我们正在构建一个“待办事项”列表应用程序。我们可以定义一个如下所示的 layout 组件

<!-- resources/views/components/layout.blade.php -->
 
<html>
<head>
<title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
<h1>Todos</h1>
<hr/>
{{ $slot }}
</body>
</html>

应用布局组件

定义 layout 组件后,我们可以创建一个使用该组件的 Blade 视图。在此示例中,我们将定义一个简单的视图,该视图显示我们的任务列表

<!-- resources/views/tasks.blade.php -->
 
<x-layout>
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>

请记住,注入到组件中的内容将提供给我们的 layout 组件中的默认 $slot 变量。正如您可能已经注意到的那样,我们的 layout 还会尊重 $title 插槽(如果提供了);否则,将显示默认标题。我们可以使用组件文档中讨论的标准插槽语法,从我们的任务列表视图中注入自定义标题

<!-- resources/views/tasks.blade.php -->
 
<x-layout>
<x-slot:title>
Custom Title
</x-slot>
 
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>

现在我们已经定义了我们的布局和任务列表视图,我们只需要从路由返回 task 视图

use App\Models\Task;
 
Route::get('/tasks', function () {
return view('tasks', ['tasks' => Task::all()]);
});

使用模板继承的布局

定义布局

布局也可以通过“模板继承”来创建。这是在引入 组件 之前构建应用程序的主要方式。

要开始使用,让我们看一下一个简单的示例。首先,我们将检查页面布局。由于大多数 Web 应用程序在各种页面上保持相同的通用布局,因此将此布局定义为单个 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 指令来指定子视图应“继承”哪个布局。扩展 Blade 布局的视图可以使用 @section 指令将内容注入到布局的部分中。请记住,如上面的示例所示,这些部分的内容将使用 @yield 显示在布局中

<!-- 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 指令来附加(而不是覆盖)内容到布局的侧边栏。当视图被渲染时,@parent 指令将被布局的内容替换。

lightbulb

与前面的示例相反,此 sidebar 部分以 @endsection 而不是 @show 结尾。@endsection 指令将只定义一个部分,而 @show 将定义并 立即生成 该部分。

@yield 指令还接受默认值作为其第二个参数。如果要生成的 section 未定义,则将呈现此值

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

表单

CSRF 字段

每当您在应用程序中定义 HTML 表单时,您都应在表单中包含一个隐藏的 CSRF 令牌字段,以便CSRF 保护中间件可以验证请求。您可以使用 @csrf Blade 指令来生成令牌字段

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

Method 字段

由于 HTML 表单无法发出 PUTPATCHDELETE 请求,因此您需要添加一个隐藏的 _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 指令会编译为 “if” 语句,因此当属性没有错误时,您可以使用 @else 指令来渲染内容

<!-- /resources/views/auth.blade.php -->
 
<label for="email">Email address</label>
 
<input
id="email"
type="email"
class="@error('email') is-invalid @else is-valid @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

如果希望在给定布尔表达式求值为 true@push 内容,则可以使用 @pushIf 指令

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

您可以根据需要多次推送到堆栈。要渲染完整的堆栈内容,请将堆栈的名称传递给 @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 模板

有时,您可能需要将原始 Blade 模板字符串转换为有效的 HTML。您可以使用 Blade facade 提供的 render 方法来完成此操作。render 方法接受 Blade 模板字符串和一个可选的数据数组以提供给模板

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

Laravel 通过将内联 Blade 模板写入 storage/framework/views 目录来渲染它们。如果您希望 Laravel 在渲染 Blade 模板后删除这些临时文件,您可以向该方法提供 deleteCachedView 参数

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

渲染 Blade 片段

当使用诸如 Turbohtmx 之类的前端框架时,您有时可能只需要在 HTTP 响应中返回 Blade 模板的一部分。Blade “片段”允许您做到这一点。要开始使用,请将 Blade 模板的一部分放置在 @fragment@endfragment 指令中

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

然后,在渲染使用此模板的视图时,您可以调用 fragment 方法来指定只应在传出的 HTTP 响应中包含指定的片段

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

fragmentIf 方法允许您根据给定条件有条件地返回视图的片段。否则,将返回整个视图

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

fragmentsfragmentsIf 方法允许您在响应中返回多个视图片段。片段将连接在一起

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

扩展 Blade

Blade 允许您使用 directive 方法定义自己的自定义指令。当 Blade 编译器遇到自定义指令时,它将使用指令包含的表达式调用提供的回调。

以下示例创建了一个 @datetime($var) 指令,该指令格式化给定的 $var,该 $var 应该是 DateTime 的实例

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::directive('datetime', function (string $expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
}
}

正如您所见,我们将把 format 方法链接到传递给指令的任何表达式上。因此,在这个例子中,此指令生成的最终 PHP 代码将是

<?php echo ($var)->format('m/d/Y H:i'); ?>
exclamation

在更新 Blade 指令的逻辑之后,您需要删除所有缓存的 Blade 视图。可以使用 view:clear Artisan 命令删除缓存的 Blade 视图。

自定义回显处理器

如果您尝试使用 Blade “echo” 一个对象,该对象的 __toString 方法将被调用。 __toString 方法是 PHP 的内置“魔术方法”之一。但是,有时您可能无法控制给定类的 __toString 方法,例如当您与之交互的类属于第三方库时。

在这些情况下,Blade 允许您为特定类型的对象注册自定义 echo 处理程序。要实现这一点,您应该调用 Blade 的 stringable 方法。stringable 方法接受一个闭包。此闭包应类型提示它负责渲染的对象类型。通常,stringable 方法应该在应用程序的 AppServiceProvider 类的 boot 方法中调用。

use Illuminate\Support\Facades\Blade;
use Money\Money;
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::stringable(function (Money $money) {
return $money->formatTo('en_GB');
});
}

一旦定义了自定义 echo 处理程序,您就可以在 Blade 模板中简单地 echo 该对象

Cost: {{ $money }}

自定义 If 语句

当定义简单的自定义条件语句时,编写自定义指令有时比必要的更复杂。因此,Blade 提供了一个 Blade::if 方法,允许您使用闭包快速定义自定义条件指令。例如,让我们定义一个自定义条件,该条件检查应用程序配置的默认“disk”。我们可以在 AppServiceProviderboot 方法中执行此操作

use Illuminate\Support\Facades\Blade;
 
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::if('disk', function (string $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