Blade 模板
简介
Blade 是 Laravel 提供的一个简单而又强大的模板引擎。 和其他流行的 PHP 模板引擎不同,Blade 并不限制你在视图中使用原生 PHP 代码。实际上,所有 Blade 视图文件都将被编译成原生的 PHP 代码并缓存起来,除非它被修改,否则不会重新编译,这就意味着 Blade 基本上不会给你的应用增加任何负担。Blade 模板文件使用 .blade.php
作为文件扩展名,被存放在 resources/views
目录。
Blade 视图可以使用全局 view
函数从 Route 或控制器返回。当然,正如有关 views 的文档中所描述的,可以使用 view
函数的第二个参数将数据传递到 Blade 视图:
Route::get('/', function () {
return view('greeting', ['name' => 'Finn']);
});
用 Livewire 为 Blade 赋能
想让你的 Blade 模板更上一层楼,轻松构建动态界面吗?看看Laravel Livewire。Livewire 允许你编写 Blade 组件,这些组件具有动态功能,通常只能通过 React 或 Vue 等前端框架来实现,这提供了一个很好的方法来构建现代,没有复杂前端映射,基于客户端渲染,无须很多的构建步骤的 JavaScript 框架。
显示数据
你可以把变量置于花括号中以在视图中显示数据。例如,给定下方的路由:
Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});
你可以像如下这样显示 name
变量的内容:
Hello, {{ $name }}.
技巧:Blade 的{{ }}
语句将被 PHP 的htmlspecialchars
函数自动转义以防范 XSS 攻击。
你不仅限于显示传递给视图的变量的内容。你也可以回显任何 PHP 函数的结果。实际上,你可以将所需的任何 PHP 代码放入 Blade echo 语句中:
The current UNIX timestamp is {{ time() }}.
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.
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}
展示非转义数据
默认情况下, Blade {{ }}
语句将被 PHP 的 htmlspecialchars
函数自动转义以防范 XSS 攻击。如果不想你的数据被转义,那么你可使用如下的语法:
Hello, {!! $name !!}.
注意:在应用中显示用户提供的数据时请格外小心,请尽可能的使用转义和双引号语法来防范 XSS 攻击。
Blade & JavaScript 框架
由于许多 JavaScript 框架也使用「花括号」来标识将显示在浏览器中的表达式,因此,你可以使用 @
符号来表示 Blade 渲染引擎应当保持不变。例如:
<h1>Laravel</h1>
Hello, @{{ name }}.
在这个例子中, @
符号将被 Blade 移除;当然,Blade 将不会修改 {{ name }}
表达式,取而代之的是 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_encode
。 from
方法接受与 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>
注意:你应该只使用Js::from
渲染已经存在的变量为 JSON。 Blade 模板基于正则表达式,如果尝试将复杂表达式传递给Js::from
可能会导致无法预测的错误。
@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)
有一条记录
@elseif (count($records) > 1)
有多条记录
@else
没有记录
@endif
为了方便, Blade 还提供了一个 @unless
指令:
@unless (Auth::check())
你还没有登录
@endunless
译注:相当于 @if (! Auth::check()) @endif
除了上面所说条件指令外, @isset
和 @empty
指令亦可作为它们所对应的 PHP 函数的快捷方式:
@isset($records)
// $records 已经被定义且不为 null ……
@endisset
@empty($records)
// $records 为「空」……
@endempty
授权指令
@auth
和 @guest
指令可用于快速判断当前用户是否已经获得 授权 或是游客:
@auth
// 用户已经通过认证……
@endauth
@guest
// 用户没有通过认证……
@endguest
如有需要,你亦可在使用 @auth
和 @guest
指令时指定 认证守卫:
@auth('admin')
// 用户已经通过认证...
@endauth
@guest('admin')
// 用户没有通过认证...
@endguest
环境指令
你可以使用 @production
指令来判断应用是否处于生产环境:
@production
// 生产环境特定内容...
@endproduction
或者,你可以使用 @env
指令来判断应用是否运行于指定的环境:
@env('staging')
// 应用运行于「staging」环境...
@endenv
@env(['staging', 'production'])
// 应用运行于 「staging」或 [生产] 环境...
@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
技巧:在遍历 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
Loop 变量
在遍历 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
如果你处于嵌套循环中,你可以使用循环的 $loop
变量的 parent
属性访问父级循环:
@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 样式
该 @class
指令有条件地编译 CSS class 样式。该指令接收一个数组,其中数组的键包含你希望添加的一个或多个样式的类名,而值是一个布尔表达式。如果数组元素有一个数值的键,它将始终包含在呈现的 class 列表中:
@php
$isActive = false;
$hasError = true;
@endphp
<span @class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>
<span class="p-4 text-gray-500 bg-red"></span>
同样,@style
指令可用于有条件地将内联 CSS 样式添加到一个 HTML 元素中。
@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 复选框输入是否被「选中(checked)」。如果提供的条件判断为 true
,则此指令将回显 checked
:
<input type="checkbox"
name="active"
value="active"
@checked(old('active', $user->active)) />
同样,该 @selected
指令可用于判断给定的选项是否被「选中(selected)」:
<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>
此外,该 @disabled
指令可用于判断给定元素是否为「禁用(disabled)」:
<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
此外,@readonly
指令可以用来指示某个元素是否应该是「只读 (readonly)」的。
<input type="email"
name="email"
value="email@laravel.com"
@readonly($user->isNotAdmin()) />
此外,@required
指令可以用来指示一个给定的元素是否应该是「必需的(required)」。
<input type="text"
name="title"
value="title"
@required($user->isAdmin()) />
包含子视图
技巧:虽然你可以自由使用该@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'])
如果想要使用 @include
包含一个给定值为 true
或 false
的布尔表达式的视图,那么你可以使用 @includeWhen
和 @includeUnless
指令:
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])
如果想要包含一个视图数组中第一个存在的视图,你可以使用 includeFirst
指令:
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
注意:在视图中,你应该避免使用__DIR__
和__FILE__
这些常量,因为他们将引用已缓存的和已编译的视图。
为集合渲染视图
你可以使用 Blade 的 @each
指令将循环合并在一行内:
@each('view.name', $jobs, 'job')
该 @each
指令的第一个参数是数组或集合中的元素的要渲染的视图片段。第二个参数是你想要迭代的数组或集合,当第三个参数是一个表示当前迭代的视图的变量名。因此,如果你遍历一个名为 jobs
的数组,通常会在视图片段中使用 job
变量来访问每一个 job (jobs 数组的元素)。在你的视图片段中,可以使用 key
变量来访问当前迭代的键。
你亦可传递第四个参数给 @each
指令。当给定的数组为空时,将会渲染该参数所对应的视图。
@each('view.name', $jobs, 'job', 'view.empty')
注意:通过@each
指令渲染的视图不会继承父视图的变量。如果子视图需要使用这些变量,你可以使用@foreach
和@include
来代替它。
@once
指令
该 @once
指令允许你定义模板的一部分内容,这部分内容在每一个渲染周期中只会被计算一次。该指令在使用 堆栈 推送一段特定的 JavaScript 代码到页面的头部环境下是很有用的。例如,如果你想要在循环中渲染一个特定的 组件 ,你可能希望仅在组件渲染的首次推送 JavaScript 代码到头部:
@once
@push('scripts')
<script>
// 你自定义的 JavaScript 代码...
</script>
@endpush
@endonce
由于该 @once
指令经常与 @push
或 @prepend
指令一起使用,为了使用方便,我们提供了 @pushOnce
和 @prependOnce
指令:
@pushOnce('scripts')
<script>
// 你自定义的 JavaScript 代码...
</script>
@endPushOnce
原始 PHP 语法
在许多情况下,嵌入 PHP 代码到你的视图中是很有用的。你可以在模板中使用 Blade 的 @php
指令执行原生的 PHP 代码块:
@php
$counter = 1;
@endphp
如果只需要写一条 PHP 语句,可以在 @php
指令中包含该语句。
@php($counter = 1)
注释
Blade 也允许你在视图中定义注释。但是,和 HTML 注释不同, Blade 注释不会被包含在应用返回的 HTML 中:
{{-- 这个注释将不会出现在渲染的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
上面的命令将在目录中创建一个 Input
组件, App\View\Components\Forms
视图将放置在 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 />
作为组件呈现。
手动注册包组件
当为你自己的应用编写组件的时候,Laravel 将会自动发现位于 app/View/Components
目录和 resources/views/components
目录中的组件。
当然,如果你使用 Blade 组件编译一个包,你可能需要手动注册组件类及其 HTML 标签别名。你应该在包的服务提供者的 boot
方法中注册你的组件:
use Illuminate\Support\Facades\Blade;
/**
* 注册你的包的服务
*/
public function boot(): void
{
Blade::component('package-alert', Alert::class);
}
当组件注册完成后,便可使用标签别名来对其进行渲染。
<x-package-alert/>
或者,你可以使用该 componentNamespace
方法按照约定自动加载组件类。例如,一个 Nightshade
包可能有 Calendar
和 ColorPicker
组件驻留在 Package\Views\Components
命名空间中:
use Illuminate\Support\Facades\Blade;
/**
* 注册你的包的服务
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\Views\Components', 'nightshade');
}
这将允许通过供应商命名空间使用包组件,使用以下 package-name::
语法:
<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/>
如果你想有条件地渲染你的组件,你可以在你的组件类上定义一个 shouldRender
方法。如果 shouldRender
方法返回 false
,该组件将不会被渲染。
use Illuminate\Support\Str;
/**
* 该组件是否应该被渲染
*/
public function shouldRender(): bool
{
return Str::length($this->message) > 0;
}
传递数据到组件中
你可以使用 HTML 属性传递数据到 Blade 组件中。普通的值可以通过简单的 HTML 属性来传递给组件。PHP 表达式和变量应该通过以 :
字符作为前缀的变量来进行传递:
<x-alert type="error" :message="$message"/>
你应该在类的构造器中定义组件的必要数据。在组件的视图中,组件的所有 public 类型的属性都是可用的。不必通过组件类的 render
方法传递:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class Alert extends Component
{
/**
* 创建组件实例。
*/
public function __construct(
public string $type,
public string $message,
) {}
/**
* 获取代表该组件的视图/内容
*/
public function render(): View
{
return view('components.alert');
}
}
渲染组件时,你可以回显变量名来显示组件的 public 变量的内容:
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>
命名方式(Casing)
组件的构造器的参数应该使用 驼峰式
类型,在 HTML 属性中引用参数名时应该使用 短横线隔开式 kebab-case :单词与单词之间使用短横线 - 进行连接)
。例如,给定如下的组件构造器:
/**
* 创建一个组件实例
*/
public function __construct(
public string $alertType,
) {}
$alertType
参数可以像这样使用:
<x-alert alert-type="danger" />
短属性语法/省略属性语法
当向组件传递属性时,你也可以使用「短属性语法/省略属性语法」(省略属性书写)。这通常很方便,因为属性名称经常与它们对应的变量名称相匹配。
{{-- 短属性语法/省略属性语法... --}}
<x-profile :$userId :$name />
{{-- 等价于... --}}
<x-profile :user-id="$userId" :name="$name" />
转义属性渲染
因为一些 JavaScript 框架,例如 Alpine.js 还可以使用冒号前缀属性,你可以使用双冒号 (::
) 前缀通知 Blade 属性不是 PHP 表达式。例如,给定以下组件:
<x-button ::class="{ danger: isDeleting }">
Submit
</x-button>
Blade 将渲染出以下 HTML 内容:
<button :class="{ danger: isDeleting }">
Submit
</button>
组件方法
除了组件模板可用的公共变量外,还可以调用组件上的任何公共方法。例如,假设一个组件有一个 isSelected
方法:
/**
* 确定给定选项是否为当前选定的选项。
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}
你可以通过调用与方法名称匹配的变量,从组件模板执行此方法:
<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
{{ $label }}
</option>
访问组件类中的属性和插槽
Blade 组件还允许你访问类的 render 方法中的组件名称、属性和插槽。但是,为了访问这些数据,应该从组件的 render
方法返回闭包。闭包将接收一个 $data
数组作为它的唯一参数。此数组将包含几个元素,这些元素提供有关组件的信息:
use Closure;
/**
* 获取表示组件的视图 / 内容
*/
public function render(): Closure
{
return function (array $data) {
// $data['componentName'];
// $data['attributes'];
// $data['slot'];
return '<div>Components content</div>';
};
}
componentName
等于 x-
前缀后面的 HTML 标记中使用的名称。所以 <x-alert />
的 componentName
将是 alert
。 attributes
元素将包含 HTML 标记上的所有属性。 slot
元素是一个 Illuminate\Support\HtmlString
实例,包含组件的插槽内容。
闭包应该返回一个字符串。如果返回的字符串与现有视图相对应,则将呈现该视图;否则,返回的字符串将作为内联 Blade 视图进行计算。
附加依赖项
如果你的组件需要引入来自 Laravel 的 服务容器的依赖项,你可以在组件的任何数据属性之前列出这些依赖项,这些依赖项将由容器自动注入:
use App\Services\AlertCreator;
/**
* 创建组件实例
*/
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
{
/**
* 不应向组件模板公开的属性/方法。
*
* @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 }}>
<!-- 组件内容 -->
</div>
注意:此时不支持在组件中使用诸如@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>
技巧:如果你需要有条件地编译不应接收合并属性的其他 HTML 元素上的类,你可以使用 @class
指令。
非 class 属性的合并
当合并非 class
属性的属性时,提供给 merge
方法的值将被视为该属性的「default」值。但是,与 class
属性不同,这些属性不会与注入的属性值合并。相反,它们将被覆盖。例如, button
组件的实现可能如下所示:
<button {{ $attributes->merge(['type' => 'button']) }}>
{{ $slot }}
</button>
若要使用自定义 type
呈现按钮组件,可以在使用该组件时指定它。如果未指定 type
,则将使用 button
作为 type 值:
<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
你可以使用 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>
有时候一个组件可能需要在它内部的不同位置放置多个不同的插槽。我们来修改一下 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>
作用域插槽
如果你使用诸如 Vue 这样的 JavaScript 框架,那么你应该很熟悉「作用域插槽」,它允许你从插槽中的组件访问数据或者方法。 Laravel 中也有类似的用法,只需在你的组件中定义 public 方法或属性,并且使用 $component
变量来访问插槽中的组件。在此示例中,我们将假设组件在其组件类上定义了 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
方法中返回组件的内容:
/**
* 获取组件的视图 / 内容。
*/
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
组件, 根据运行时的值或变量来渲染组件:
<x-dynamic-component :component="$componentName" class="mt-4" />
手动注册组件
注意:以下关于手动注册组件的文档主要适用于那些正在编写包含视图组件的 Laravel 包的用户。如果你不是在写包,这一部分的组件文档可能与你无关。
当为自己的应用程序编写组件时,组件会在app/View/Components
目录和resources/views/components
目录下被自动发现。
但是,如果你正在建立一个利用 Blade 组件的包,或者将组件放在非传统的目录中,你将需要手动注册你的组件类和它的 HTML 标签别名,以便 Laravel 知道在哪里可以找到这个组件。你通常应该在你的包的服务提供者的boot
方法中注册你的组件:
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;
/**
* 注册你的包的服务。
*/
public function boot(): void
{
Blade::component('package-alert', AlertComponent::class);
}
一旦你的组件被注册,它就可以使用它的标签别名进行渲染。
<x-package-alert/>
自动加载包组件
另外,你可以使用componentNamespace
方法来自动加载组件类。例如,一个Nightshade
包可能有Calendar
和ColorPicker
组件,它们位于PackageViews\Components
命名空间中。
use Illuminate\Support\Facades\Blade;
/**
* 注册你的包的服务。
*/
public function boot(): void
{
Blade::componentNamespace('Nightshade\Views\Components', 'nightshade');
}
这将允许使用package-name::
语法的供应商名称空间来使用包的组件。
<x-nightshade::calendar />
<x-nightshade::color-picker />
Blade 将通过组件名称的驼峰式大小写 (pascal-casing) 自动检测与该组件链接的类。也支持使用 "点 "符号的子目录。
匿名组件
与行内组件相同,匿名组件提供了一个通过单个文件管理组件的机制。然而,匿名组件使用的是一个没有关联类的单一视图文件。要定义一个匿名组件,你只需将 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
渲染组件, 我们被迫将「索引」组件模板放置在 resources/views/components
目录中,而不是与其他相关的模板嵌套在 accordion
目录中。
幸运的是,Blade 允许你 index.blade.php
在组件的模板目录中放置文件。当 index.blade.php
组件存在模板时,它将被呈现为组件的「根」节点。因此,我们可以继续使用上面示例中给出的相同 Blade 语法;但是,我们将像这样调整目录结构:
/resources/views/components/accordion/index.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>
注意:该@aware
指令无法访问未通过 HTML 属性显式传递给父组件的父数据。@aware
指令 不能访问未显式传递给父组件的默认值@props
。
匿名组件路径
如前所述,匿名组件通常是通过在你的resources/views/components
目录下放置一个 Blade 模板来定义的。然而,你可能偶尔想在 Laravel 注册其他匿名组件的路径,除了默认路径。
anonymousComponentPath
方法接受匿名组件位置的「路径」作为它的第一个参数,并接受一个可选的「命名空间」作为它的第二个参数,组件应该被放在这个命名空间下。通常,这个方法应该从你的应用程序的一个服务提供者 的boot
方法中调用。
/**
* 引导任何应用服务。
*/
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 组件 并在整个应用程序中非常方便地使用它。
定义布局组件
例如,假设我们正在构建一个「todo list」应用程序。我们可以定义如下所示的 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)
{{ $task }}
@endforeach
</x-layout>
请记住,注入到组件中的内容将提供给 layout
组件中的默认 $slot
变量。正如你可能已经注意到的,如果提供了 $title
插槽,那么我们的 layout
也会尊从该插槽;否则,将显示默认的标题。我们可以使用组件文档中讨论的标准槽语法从任务列表视图中插入自定义标题。 我们可以使用组件文档中讨论的标准插槽语法从任务列表视图中注入自定义标题:
<!-- resources/views/tasks.blade.php -->
<x-layout>
<x-slot:title>
Custom Title
</x-slot>
@foreach ($tasks as $task)
{{ $task }}
@endforeach
</x-layout>
现在我们已经定义了布局和任务列表视图,我们只需要从路由中返回 task
视图即可:
use App\Models\Task;
Route::get('/tasks', function () {
return view('tasks', ['tasks' => Task::all()]);
});
使用模板继承进行布局
定义一个布局
布局也可以通过 「模板继承」 创建。在引入 组件 之前,这是构建应用程序的主要方法。
让我们看一个简单的例子做开头。首先,我们将检查页面布局。由于大多数 web 应用程序在不同的页面上保持相同的总体布局,因此将此布局定义为单一视图非常方便:
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('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
指令将被布局的内容替换。
技巧:与前面的示例相反,本sidebar
节以@endsection
结束,而不是以@show
结束。@endsection
指令将只定义一个节,@show
将定义并 立即 yield 该节。
该 @yield
指令还接受默认值作为其第二个参数。如果要生成的节点未定义,则将呈现此内容:
@yield('content', 'Default content')
表单
CSRF 字段
无论何时在应用程序中定义 HTML 表单,都应该在表单中包含一个隐藏的 CSRF 令牌字段,以便 CSRF 保护中间件 可以验证请求。你可以使用 @csrf
Blade 指令生成令牌字段:
<form method="POST" action="/profile">
@csrf
...
</form>
Method 字段
由于 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
指令编译为「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
门面提供的 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 片段
当使用 Turbo 和 htmx 等前端框架时,你可能偶尔需要在你的HTTP响应中只返回Blade模板的一个部分。Blade「片段(fragment)」允许你这样做。要开始,将你的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');
fragments
和 fragmentsIf
方法允许你在响应中返回多个视图片段。这些片段将被串联起来。
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
,它应该是 DateTime
的一个实例:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 注册应用的服务
*/
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'); ?>
注意:更新 Blade 指令的逻辑后,需要删除所有缓存的 Blade 视图。可以使用 view:clear
Artisan 命令。
自定义回显处理程序
如果你试图使用 Blade 来「回显」一个对象, 该对象的 __toString
方法将被调用。该__toString
方法是 PHP 内置的「魔术方法」之一。但是,有时你可能无法控制 __toString
给定类的方法,例如当你与之交互的类属于第三方库时。
在这些情况下,Blade 允许您为该特定类型的对象注册自定义回显处理程序。为此,您应该调用 Blade 的 stringable
方法。该 stringable
方法接受一个闭包。这个闭包类型应该提示它负责呈现的对象的类型。通常,应该在应用程序的 AppServiceProvider
类的 boot
方法中调用该 stringable
方法:
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');
});
}
定义自定义回显处理程序后,您可以简单地回显 Blade 模板中的对象:
Cost: {{ $money }}
自定义 if 声明
在定义简单的自定义条件语句时,编写自定义指令通常比较复杂。因此,Blade 提供了一个 Blade::if 方法,允许你使用闭包快速定义自定义条件指令。例如,让我们定义一个自定义条件来检查为应用程序配置的默认 「存储」。我们可以在 AppServiceProvider 的 boot 方法中执行此操作:
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