Blade 模板
简介
Blade 是 Laravel 附带的简单而强大的模板引擎。与某些 PHP 模板引擎不同,Blade 不限制你在模板中使用原生 PHP 代码。事实上,所有 Blade 模板都被编译为原生 PHP 代码并缓存直到被修改,这意味着 Blade 基本上为你的应用程序增加了零开销。Blade 模板文件使用 .blade.php 文件扩展名,通常存储在 resources/views 目录中。
Blade 视图可以使用全局 view 助手从路由或控制器返回。当然,如视图文档中所述,可以使用 view 助手的第二个参数将数据传递给 Blade 视图:
Route::get('/', function () {
return view('greeting', ['name' => 'Finn']);
});使用 Livewire 增强 Blade
想要将你的 Blade 模板提升到一个新的水平并轻松构建动态界面?查看 Laravel Livewire。Livewire 允许你编写 Blade 组件,这些组件增强了动态功能,通常只有通过 React、Svelte 或 Vue 等前端框架才能实现,提供了一种很好的方法来构建现代、响应式前端,而没有许多 JavaScript 框架的复杂性、客户端渲染或构建步骤。
显示数据
你可以通过将变量包裹在花括号中来显示传递给 Blade 视图的数据。例如,给定以下路由:
Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});你可以像这样显示 name 变量的内容:
Hello, {{ $name }}.NOTE
Blade 的 echo 语句会自动通过 PHP 的 htmlspecialchars 函数处理以防止 XSS 攻击。
你不仅限于显示传递给视图的变量内容。你也可以输出任何 PHP 函数的结果。事实上,你可以在 Blade echo 语句中放置任何你想要的 PHP 代码:
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
{
/**
* 启动任何应用服务。
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}显示未转义数据
默认情况下,Blade 语句会自动通过 PHP 的 htmlspecialchars 函数处理以防止 XSS 攻击。如果你不希望数据被转义,可以使用以下语法:
Hello, {!! $name !!}.WARNING
在输出应用程序用户提供的内容时要非常小心。在显示用户提供的数据时,通常应该使用转义的双花括号语法来防止 XSS 攻击。
Blade 与 JavaScript 框架
由于许多 JavaScript 框架也使用「花括号」来表示应在浏览器中显示的表达式,你可以使用 @ 符号通知 Blade 渲染引擎表达式应保持不变。例如:
<h1>Laravel</h1>
Hello, @{{ name }}.在此示例中,@ 符号将被 Blade 移除;然而, 表达式将保持不变,允许你的 JavaScript 框架渲染它。
@ 符号也可用于转义 Blade 指令:
{{-- Blade 模板 --}}
@@if()
<!-- HTML 输出 -->
@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>WARNING
你应该只使用 Js::from 方法将现有变量渲染为 JSON。Blade 模板基于正则表达式,尝试将复杂表达式传递给指令可能会导致意外失败。
@verbatim 指令
如果你在模板的大部分内容中显示 JavaScript 变量,你可以使用 @verbatim 指令包裹 HTML,这样你就不必在每个 Blade echo 语句前加上 @ 符号:
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatimBlade 指令
除了模板继承和显示数据外,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 已定义且不为 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" 或 "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上下文指令
@context 指令可用于确定上下文值是否存在。如果上下文值存在,则 @context 和 @endcontext 指令内的模板内容将被评估。在 @context 指令的内容中,你可以输出 $value 变量来显示上下文值:
@context('canonical')
<link href="{{ $value }}" rel="canonical">
@endcontextSwitch 语句
可以使用 @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>
@endwhileNOTE
在遍历 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"
value="email@laravel.com"
@readonly($user->isNotAdmin())
/>此外,@required 指令可用于指示给定元素是否应「必填」:
<input
type="text"
name="title"
value="title"
@required($user->isAdmin())
/>包含子视图
NOTE
虽然你可以自由使用 @include 指令,但 Blade 组件提供了类似的功能,并提供了比 @include 指令更多的优势,如数据和属性绑定。
Blade 的 @include 指令允许你从另一个视图中包含 Blade 视图。父视图可用的所有变量都将可用于包含的视图:
<div>
@include('shared.errors')
<form>
<!-- 表单内容 -->
</form>
</div>尽管包含的视图将继承父视图中可用的所有数据,你也可以传递一组额外的数据,这些数据应可用于包含的视图:
@include('view.name', ['status' => 'complete'])如果你尝试 @include 一个不存在的视图,Laravel 将抛出错误。如果你想包含一个可能存在或不存在的视图,你应该使用 @includeIf 指令:
@includeIf('view.name', ['status' => 'complete'])如果你想在给定的布尔表达式评估为 true 或 false 时 @include 一个视图,你可以使用 @includeWhen 和 @includeUnless 指令:
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])要从给定的视图数组中包含第一个存在的视图,你可以使用 includeFirst 指令:
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])如果你想包含一个视图而不继承父视图的任何变量,你可以使用 @includeIsolated 指令。包含的视图将只能访问你显式传递的变量:
@includeIsolated('view.name', ['user' => $user])WARNING
你应该避免在 Blade 视图中使用 __DIR__ 和 __FILE__ 常量,因为它们将引用缓存的编译视图的位置。
为集合渲染视图
你可以使用 Blade 的 @each 指令将循环和包含合并为一行:
@each('view.name', $jobs, 'job')@each 指令的第一个参数是要为集合中每个元素渲染的视图。第二个参数是你希望遍历的集合或数组,而第三个参数是应传递给视图的迭代视图中的变量名。因此,例如,如果你正在遍历 jobs 数组,你可能希望在每个视图中使用 $job 变量访问每个作业。
你可以将第四个参数传递给 @each 指令,该参数指定如果给定数组为空时应渲染的视图:
@each('view.name', $jobs, 'job', 'view.empty')WARNING
通过 @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 语句来导入类,你可以使用 @use 指令:
@use('App\Models\Flight')你可以提供第二个参数来为导入的类起别名:
@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 目录中,视图放在 resources/views/components 目录中:
php artisan make:component Alertmake:component 命令还将为组件创建视图模板。编写组件时,组件类通常在 app/View/Components 目录中,视图在 resources/views/components 目录中。
对于在子目录中嵌套的组件,你可以在生成组件时使用 . 符号表示法:
php artisan make:component Forms/Input上面的命令将在 app/View/Components/Forms 目录中创建一个 Input 组件,视图放在 resources/views/components/forms 目录中。
手动注册包组件
在编写供你自己的应用程序使用的组件时,组件会在 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/>渲染组件
要显示组件,你可以在一个 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;
}索引组件
有时,当组件由许多 Blade 模板组成时,你可能希望将给定组件的模板分组到单个目录中。例如,想象一个具有以下目录结构的「accordion」组件:
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php此目录结构允许你像这样渲染手风琴组件及其项目:
<x-accordion>
<x-accordion.item>
...
</x-accordion.item>
</x-accordion>然而,为了通过 x-accordion 渲染手风琴组件,我们被迫将「索引」手风琴组件模板放在 resources/views/components 目录中,而不是将其与其他手风琴相关模板嵌套在 accordion 目录中。
幸运的是,Blade 允许你在组件目录本身内放置一个与组件目录名称匹配的文件。当此模板存在时,即使它嵌套在目录中,也可以将其渲染为组件的「根」元素。因此,我们可以继续使用上面示例中给出的相同 Blade 语法;然而,我们将像这样调整目录结构:
/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php向组件传递数据
你可以使用 HTML 属性将数据传递给 Blade 组件。原生 HTML 属性可以使用简单的 HTML 属性字符串传递给组件。PHP 表达式和变量应该通过使用 : 字符作为前缀的属性传递给组件:
<x-alert type="error" :message="$message"/>你应该在组件类构造函数中定义组件的所有数据属性。组件类的所有公共属性将自动可用于组件视图。不必通过组件的 render 方法将数据传递给视图:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Alert extends Component
{
/**
* 创建组件实例。
*/
public function __construct(
public string $type,
public string $message,
) {}
/**
* 获取代表组件的视图/内容。
*/
public function render(): string
{
return view('components.alert');
}
}渲染组件时,你可以通过回显变量名称来显示组件公共属性的内容:
<div class="alert alert-{{ $type }}">
{{ $message }}
</div>大小写
组件构造函数参数应该使用 camelCase 指定,而在 HTML 属性中引用参数名称时应该使用 kebab-case。例如,给定以下组件构造函数:
/**
* 创建组件实例。
*/
public function __construct(
public string $alertType,
) {}$alertType 参数可以这样提供给组件:
<x-alert alert-type="danger" />短属性语法
向组件传递属性时,你也可以使用「短属性语法」。这通常很方便,因为属性名称通常与它们对应的变量名称匹配。例如,不这样做:
<x-alert :message="$message" />你可以这样做:
<x-alert :$message />转义属性插值
因为一些 JavaScript 框架(如 Alpine.js)也使用冒号前缀属性,你可以使用双冒号(::)前缀来通知 Blade 该属性不是 PHP 表达式。例如,给定以下组件:
<x-button ::class="{ danger: isDangerous }">
Submit
</x-button>Blade 将渲染以下 HTML:
<button :class="{ danger: isDangerous }">
Submit
</button>组件方法
除了组件模板可用的公共属性外,组件上的任何公共方法也可以被调用。例如,想象一个具有 isSelected 方法的组件:
/**
* 确定给定选项是否为当前选中的选项。
*/
public function isSelected(string $option): bool
{
return $option === $this->selected;
}你可以通过调用与方法名称匹配的变量来从组件模板执行此方法:
<option {{ $isSelected($value) ? 'selected="selected"' : '' }} value="{{ $value }}">
{{ $label }}
</option>在组件类中访问属性和插槽
Blade 组件还允许你访问渲染方法中的组件名称、属性和插槽。但是,要访问此数据,你应该从组件的 render 方法返回闭包:
use Closure;
/**
* 获取代表组件的视图/内容。
*/
public function render(): Closure
{
return function () {
return '<div {{ $attributes }}>Components content</div>';
};
}组件的 render 方法返回的闭包可以声明 $data、$attributes 和 $slot 参数。这些参数提供有关组件标签中包含的数据、属性和插槽的信息:
use Closure;
/**
* 获取代表组件的视图/内容。
*/
public function render(): Closure
{
return function (array $data) {
// $data['componentName'];
// $data['attributes'];
// $data['slot'];
return '<div {{ $attributes }}>Components content</div>';
};
}componentName 等于 x- 前缀之后标签中使用的名称。因此,如果组件标签是 <x-alert />,componentName 将是 alert。attributes 元素将包含标签上可用的所有属性。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'];
/**
* 创建组件实例。
*/
public function __construct(
public string $type,
) {}
}组件属性
我们已经检查了如何将数据属性传递给组件;然而,有时你可能需要指定额外的 HTML 属性,例如 class,这些属性不是组件运行所需数据的一部分。通常,你希望将这些额外属性传递给组件模板的根元素。例如,想象我们想这样渲染一个 alert 组件:
<x-alert type="error" :message="$message" class="mt-4"/>所有不属于组件构造函数的属性将自动添加到组件的「属性包」中。此属性包通过 $attributes 变量自动可用于组件。可以通过输出此变量在组件内渲染所有属性:
<div {{ $attributes }}>
<!-- 组件内容 -->
</div>WARNING
此时不支持在组件标签内使用 @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">
<!-- $message 变量的内容 -->
</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>NOTE
如果你需要在其他不应接收合并属性的 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>
@endifhasAny 方法可用于确定组件上是否存在任何给定属性:
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>One of the attributes is present</div>
@endif你可以使用 get 方法检索特定属性的值:
{{ $attributes->get('class') }}only 方法可用于仅检索具有给定键的属性:
{{ $attributes->only(['class']) }}except 方法可用于检索除具有给定键的属性之外的所有属性:
{{ $attributes->except(['class']) }}保留关键字
默认情况下,某些关键字保留供 Blade 内部使用以渲染组件。以下关键字不能在组件中定义为公共属性或方法名称:
datarenderresolveresolveViewshouldRenderviewwithAttributeswithName
插槽
你通常需要通过「插槽」向组件传递额外内容。组件插槽通过输出 $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>你可以调用插槽的 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 方法返回组件的标记:
/**
* 获取代表组件的视图/内容。
*/
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" />手动注册组件
WARNING
以下关于手动注册组件的文档主要适用于那些正在编写包含视图组件的 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 组件,它们位于 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 将通过将组件名称转换为 Pascal 大小写来自动检测链接到此组件的类。也支持使用「点」表示法的子目录。
匿名组件
与内联组件类似,匿名组件提供了一种通过单个文件管理组件的机制。然而,匿名组件使用单个视图文件,没有关联的类。要定义匿名组件,你只需要将 Blade 模板放在 resources/views/components 目录中。例如,假设你在 resources/views/components/alert.blade.php 定义了一个组件,你可以像这样简单地渲染它:
<x-alert/>你可以使用 . 字符来指示组件是否嵌套在 components 目录更深处。例如,假设组件定义在 resources/views/components/inputs/button.blade.php,你可以像这样渲染它:
<x-inputs.button/>要通过 Artisan 创建匿名组件,你可以在调用 make:component 命令时使用 --view 标志:
php artisan make:component forms.input --view上面的命令将在 resources/views/components/forms/input.blade.php 创建一个 Blade 文件,可以通过 <x-forms.input /> 作为组件渲染。
匿名索引组件
有时,当组件由许多 Blade 模板组成时,你可能希望将给定组件的模板分组到单个目录中。例如,想象一个具有以下目录结构的「accordion」组件:
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php此目录结构允许你像这样渲染手风琴组件及其项目:
<x-accordion>
<x-accordion.item>
...
</x-accordion.item>
</x-accordion>然而,为了通过 x-accordion 渲染手风琴组件,我们被迫将「索引」手风琴组件模板放在 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 prop 只传递给了父组件(<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>WARNING
@aware 指令无法访问未通过 HTML 属性显式传递给父组件的父数据。未显式传递给父组件的默认 @props 值无法被 @aware 指令访问。
匿名组件路径
如前所述,匿名组件通常通过将 Blade 模板放在 resources/views/components 目录中来定义。然而,除了默认路径外,你可能偶尔希望向 Laravel 注册其他匿名组件路径。
例如,假设你正在构建一个包含 Blade 组件的包,你可能希望注册一个额外的匿名组件路径:
use Illuminate\Support\Facades\Blade;
/**
* 启动你的包的服务。
*/
public function boot(): void
{
Blade::anonymousComponentPath(
__DIR__.'/../resources/views/components', 'laravel-package'
);
}第二个参数定义了组件的「命名空间」,允许你在渲染时使用 laravel-package:: 前缀:
<x-laravel-package::alert />如果没有提供第二个参数,组件将像往常一样渲染,不带命名空间前缀:
<x-alert />构建布局
使用组件构建布局
大多数 Web 应用程序在各种页面上保持相同的通用布局。如果我们必须在每个视图中重复整个布局 HTML,维护我们的应用程序将变得非常繁琐和困难。幸运的是,将此布局定义为单个 Blade 组件 并在整个应用程序中使用非常方便。
例如,想象一个「layout」组件:
<!-- resources/views/components/layout.blade.php -->
<html>
<head>
<title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
<h1>Todo Manager</h1>
<hr/>
{{ $slot }}
</body>
</html>一旦定义了布局组件,我们就可以创建一个使用该布局组件的 Blade 视图:
<!-- resources/views/tasks.blade.php -->
<x-layout>
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout>要包含在布局中的内容将使用 $slot 变量注入到 layout 组件中。你可能已经注意到,如果提供了 $title 插槽,布局将管理 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()]);
});两层布局
有时你的应用程序可能有不同的布局,但它们共享页眉和页脚。你可以使用嵌套布局来实现这一点。
首先,定义一个「base」布局:
<!-- resources/views/components/layout-base.blade.php -->
<html>
<head>
<title>{{ $title ?? 'Todo Manager' }}</title>
</head>
<body>
<h1>Todo Manager</h1>
<hr/>
{{ $slot }}
</body>
</html>然后,定义一个继承基础布局的「app」布局:
<!-- resources/views/components/layout-app.blade.php -->
<x-layout-base>
<x-slot:title>
App Layout - {{ $title }}
</x-slot>
<nav>
<a href="/">Home</a>
<a href="/tasks">Tasks</a>
</nav>
{{ $slot }}
</x-layout-base>现在,你的视图可以继承 app 布局,而 app 布局又继承 base 布局:
<!-- resources/views/tasks.blade.php -->
<x-layout-app>
<x-slot:title>
Tasks
</x-slot>
@foreach ($tasks as $task)
<div>{{ $task }}</div>
@endforeach
</x-layout-app>使用模板继承构建布局
NOTE
这是关于布局的经典方法。我们建议你改用本文档中描述的组件方法。
在传统 PHP 模板中,布局通常通过「模板继承」来管理。这是最复杂的布局方法。
定义布局
让我们看一个简单的例子。首先,我们将检查一个「主」页面布局。由于大多数 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 指令将被布局的内容替换。
NOTE
与上一个示例相反,此 sidebar 区块以 @endsection 结尾而不是 @show。@endsection 指令仅定义一个区块,而 @show 将定义并立即生成该区块。
@yield 指令还接受一个默认值作为第二个参数。如果要生成的区块未定义,则将渲染此内容:
@yield('content', 'Default content')表单
CSRF 字段
任何时候在应用程序中定义 HTML 表单时,都应该在表单中包含一个隐藏的 CSRF 令牌字段,以便CSRF 保护中间件可以验证请求。你可以使用 @csrf Blade 指令生成令牌字段:
<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 和 @enderror 指令内,你可以输出 $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」语句,你可以在 @error 指令内使用 @else 指令来检测属性没有错误时:
<!-- /resources/views/post/create.blade.php -->
<label for="title">Post Title</label>
<input id="title"
type="text"
class="@error('title') is-invalid @else is-valid @enderror"
/>你可以将特定错误包的名称作为第二个参数传递给 @error 指令,以便在包含多个表单的页面上检索验证错误消息:
<input ... class="@error('title', 'login') is-invalid @enderror">堆栈
Blade 允许你推送到命名堆栈,以便在另一个视图或布局中渲染。这在子视图中指定所需的 JavaScript 库时特别有用:
@push('scripts')
<script src="/example.js"></script>
@endpush如果你需要 @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 方法完成此操作:
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 片段
当使用前端框架如 HTMX 或 Turbo 时,你可能经常只需要在 HTTP 响应中返回 Blade 模板的一部分。Blade「片段」允许你做到这一点。要开始,将 Blade 模板的一部分包装在 @fragment 和 @endfragment 指令中:
<!-- resources/views/welcome.blade.php -->
<div>
...
@fragment('user-list')
<ul>
@foreach ($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
@endfragment
</div>然后,在渲染使用此模板的视图时,你可以调用 fragment 方法,仅将指定片段包含在响应中:
view('welcome', ['users' => $users])->fragment('user-list');fragmentIf 方法允许你根据给定条件有条件地返回片段:
view('welcome', ['users' => $users])->fragmentIf(
$request->hasHeader('HX-Request'), 'user-list'
);某些前端库如 HTMX 允许请求多个片段。你可以使用 fragments 方法返回多个片段:
view('welcome', ['users' => $users])->fragments(['user-list', 'comment-list']);fragmentsIf 方法允许你根据给定条件有条件地返回多个片段:
view('welcome', ['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
{
// ...
}
/**
* 启动任何应用服务。
*/
public function boot(): void
{
Blade::directive('datetime', function (string $expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
}
}如你所见,我们将 strtotime 函数链接到传递给指令的 $expression 上。因此,当用户传递给指令的 $expression 将被 strtotime 函数处理,然后传递给 date 函数。
WARNING
更新 Blade 指令的逻辑后,你需要删除所有缓存的 Blade 视图。可以使用 view:clear Artisan 命令删除缓存的 Blade 视图。
自定义 Echo 处理器
如果你尝试「echo」一个对象,Blade 会触发该对象的 __toString 方法。PHP 将此方法定义为对象的字符串表示形式。然而,某些类可能没有 __toString 方法,或者你可能希望自定义特定类型对象的字符串表示形式。
在这些情况下,Blade 允许你为该特定类型的对象注册自定义 echo 处理器。为此,你应该调用 Blade 的 stringable 方法。stringable 方法接受一个闭包。此闭包应该类型提示它负责渲染的对象类型。通常,stringable 方法应该在应用程序的 AppServiceProvider 类的 boot 方法中调用:
use Illuminate\Support\Facades\Blade;
use Money\Money;
/**
* 启动任何应用服务。
*/
public function boot(): void
{
Blade::stringable(function (Money $money) {
return $money->formatTo('en_GB');
});
}一旦定义了自定义 echo 处理器,你就可以在 Blade 模板中简单地输出该对象:
Cost: {{ $money }}自定义 If 语句
在定义简单的自定义条件语句时,编程自定义指令有时比必要的更复杂。因此,Blade 提供了一个 Blade::if 方法,允许你使用闭包快速定义自定义条件指令。例如,让我们定义一个自定义条件,检查应用程序配置的默认「disk」。我们可以在 AppServiceProvider 的 boot 方法中这样做:
use Illuminate\Support\Facades\Blade;
/**
* 启动任何应用服务。
*/
public function boot(): void
{
Blade::if('disk', function (string $value) {
return config('filesystems.default') === $value;
});
}一旦定义了自定义条件,你就可以在模板中使用它:
@disk('local')
<!-- 应用程序正在使用 local disk... -->
@elsedisk('s3')
<!-- 应用程序正在使用 s3 disk... -->
@else
<!-- 应用程序正在使用其他 disk... -->
@enddisk
@unlessdisk('local')
<!-- 应用程序未使用 local disk... -->
@enddisk