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']);
});
显示数据
你可以通过将变量放在大括号中来显示传递给 Blade 视图的数据。例如,给定以下路由:
Route::get('/', function () {
return view('welcome', ['name' => 'Samantha']);
});
你可以像这样显示 name
变量的内容:
Hello, {{ $name }}.
Blade 的 |
你不仅仅限于显示传递给视图的变量内容,还可以回显任何 PHP 函数的结果。实际上,你可以在 Blade 的输出语句中放入任何你想要的 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
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Blade::withoutDoubleEncoding();
}
}
Blade 与 JavaScript 框架
由于许多 JavaScript 框架也使用 “花括号” 来表示一个表达式应该在浏览器中显示,您可以使用 @
符号来告诉 Blade 渲染引擎某个表达式应该保持原样。例如:
<h1>Laravel</h1>
Hello, @{{ name }}.
在这个例子中,@
符号会被 Blade 移除;然而,{{ 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
方法会返回一个 JavaScript JSON.parse
语句,将给定的对象或数组转换为有效的 JavaScript 对象:
<script>
var app = {{ Illuminate\Support\Js::from($array) }};
</script>
最新版本的 Laravel 应用程序框架包括一个 Js
facade,提供了在 Blade 模板中方便访问该功能:
<script>
var app = {{ Js::from($array) }};
</script>
您应该仅使用 |
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
除了已经讨论过的条件指令外,@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
Switch 语句
Switch 语句可以使用 @switch
、@case
、@break
、@default
和 @endswitch
指令构建:
@switch($i)
@case(1)
第一个情况...
@break
@case(2)
第二个情况...
@break
@default
默认情况...
@endswitch
循环
除了条件语句,Blade 还提供了简单的指令来处理 PHP 的循环结构。每个指令的功能与 PHP 中的相应结构完全相同:
@for ($i = 0; $i < 10; $i++)
当前值是 {{ $i }}
@endfor
@foreach ($users as $user)
<p>这是用户 {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>没有用户</p>
@endforelse
@while (true)
<p>我正在循环</p>
@endwhile
在使用 |
在使用循环时,您还可以使用 @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)
这是第一次迭代。
@endif
@if ($loop->last)
这是最后一次迭代。
@endif
<p>这是用户 {{ $user->id }}</p>
@endforeach
如果您处于嵌套循环中,可以通过 parent
属性访问父循环的 $loop
变量:
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
这是父循环的第一次迭代。
@endif
@endforeach
@endforeach
$loop
变量还包含其它一些有用的属性:
属性 | 描述 |
---|---|
|
当前循环迭代的索引(从 0 开始)。 |
|
当前循环的迭代次数(从 1 开始)。 |
|
循环中剩余的迭代次数。 |
|
被迭代数组中的总项数。 |
|
是否是第一次迭代。 |
|
是否是最后一次迭代。 |
|
是否是偶数次迭代。 |
|
是否是奇数次迭代。 |
|
当前循环的嵌套层级。 |
|
当处于嵌套循环中时,表示父循环的 $loop 变量。 |
条件类&样式
@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())
/>
包含子视图
虽然您可以使用 |
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'])
如果您希望在给定的布尔表达式为真或为假时包含一个视图,您可以使用 @includeWhen
和 @includeUnless
指令:
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])
要从给定的视图数组中包含第一个存在的视图,您可以使用 @includeFirst
指令:
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
您应该避免在 Blade 视图中使用 |
渲染集合的视图
您可以将循环和包含结合成一行,使用 Blade 的 @each
指令:
@each('view.name', $jobs, 'job')
@each
指令的第一个参数是要为数组或集合中的每个元素渲染的视图。第二个参数是您希望迭代的数组或集合,而第三个参数是将为当前迭代分配的变量名。因此,例如,如果您正在遍历一组工作,通常您会希望在视图中将每个工作作为 job
变量访问。当前迭代的数组键将作为 key
变量在视图中可用。
您还可以传递第四个参数给 @each
指令。此参数决定如果给定的数组为空时应渲染哪个视图:
@each('view.name', $jobs, 'job', 'view.empty')
通过 |
@once 指令
@once
指令允许您定义一个仅在每次渲染周期中评估一次的模板部分。这在使用【堆栈】将特定的 JavaScript 推送到页面头部时非常有用。例如,如果您在循环中渲染某个【组件】,您可能希望仅在第一次渲染该组件时将 JavaScript 推送到头部:
@once
@push('scripts')
<script>
// 您的自定义 JavaScript...
</script>
@endpush
@endonce
由于 @once
指令通常与 @push
或 @prepend
指令一起使用,Laravel 还提供了 @pushOnce
和 @prependOnce
指令,方便您使用:
@pushOnce('scripts')
<script>
// 您的自定义 JavaScript...
</script>
@endPushOnce
组件
组件和插槽提供了与部分(section)、布局和包含(include)相似的好处;然而,有些人可能会发现组件和插槽的思维模型更容易理解。编写组件有两种方式:基于类的组件和匿名组件。
基于类的组件
要创建一个基于类的组件,您可以使用 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;
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 会自动通过 Pascal 大小写法来检测与该组件关联的类。子目录也支持使用 “点” 符号表示法。
渲染组件
要在 Blade 模板中显示一个组件,您可以使用 Blade 组件标签。Blade 组件标签以 x-
开头,后跟组件类的 kebab-case 名称:
<x-alert/>
<x-user-profile/>
如果组件类位于 app/View/Components
目录中的子目录下,您可以使用 .
字符表示目录的嵌套。例如,如果组件位于 app/View/Components/Inputs/Button.php
,则可以这样渲染它:
<x-inputs.button/>
如果您希望条件渲染组件,可以在组件类中定义一个 shouldRender
方法。如果该方法返回 false
,组件将不会被渲染:
use Illuminate\Support\Str;
public function shouldRender(): bool
{
return Str::length($this->message) > 0;
}
索引组件
有时候,组件是某个组件组的一部分,您可能希望将相关组件放在同一个目录中。例如,假设有一个 “card” 组件,其类结构如下:
`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" />
简化属性语法
在将属性传递给组件时,您还可以使用 “简短属性” 语法。这通常很方便,因为属性名称通常与它们对应的变量名称相匹配:
{{-- 简短属性语法... --}}
<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
方法:
/**
* 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>';
}
|
componentName
等于 HTML 标签中 x-
前缀后使用的名称。因此,<x-alert />
的 componentName
将是 alert
。attributes
元素将包含 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 }}>
<!-- 组件内容 -->
</div>
目前,像 |
默认值 / 合并属性
有时,您可能需要为属性指定默认值,或将额外的值合并到某些组件属性中。为此,您可以使用属性袋的 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>
如果您需要条件性地编译其它 HTML 元素的类,但这些元素不应接收合并的属性,您可以使用 |
非类属性的合并
当合并不是 class
属性时,传递给 merge
方法的值将被视为该属性的 “默认” 值。然而,与 class
属性不同,这些属性不会与注入的属性值合并,而是会被覆盖。例如,按钮组件的实现可能如下所示:
<button {{ $attributes->merge(['type' => 'button']) }}>
{{ $slot }}
</button>
要渲染按钮组件并指定自定义的 type
,可以在使用组件时指定。如果没有指定 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 属性存在</div>
@endif
如果传递一个数组给 has
方法,方法将确定组件中是否存在所有给定的属性:
@if ($attributes->has(['name', 'class']))
<div>所有属性都存在</div>
@endif
hasAny
方法可以用于确定是否存在任何给定的属性:
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
<div>至少有一个属性存在</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>
我们可以通过将内容注入到组件中来传递内容到插槽:
<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
Scoped Slots(作用域插槽)
如果你使用过像 Vue 这样的 JavaScript 框架,可能会熟悉 “作用域插槽”,它允许你在插槽中访问来自组件的数据或方法。在 Laravel 中,你也可以通过在组件上定义公共方法或属性,并通过 $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;
}
动态组件
有时你可能需要渲染一个组件,但直到运行时才知道应该渲染哪个组件。在这种情况下,你可以使用 Laravel 内置的 dynamic-component
组件,根据运行时的值或变量来渲染组件:
// $componentName = "secondary-button";
<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
组件,这些组件位于 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 大小写(驼峰式)检测与该组件关联的类。子目录也支持使用 "dot"(点)表示法。
匿名组件
与内联组件类似,匿名组件提供了一种通过单个文件管理组件的机制。然而,匿名组件只使用一个视图文件,并且没有关联的类。要定义一个匿名组件,你只需将一个 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>
需要注意的是, |
匿名组件路径
如前所述,匿名组件通常通过将 Blade 模板放置在 resources/views/components
目录中来定义。然而,有时你可能希望在默认路径之外,向 Laravel 注册其它匿名组件路径。
anonymousComponentPath
方法接受两个参数,第一个参数是匿名组件的位置路径,第二个参数是可选的命名空间,用于指定组件应放置在哪个命名空间下。通常,这个方法应该在应用程序的【服务提供者】的 boot
方法中调用:
/**
* 启动应用程序的任何服务。
*/
public function boot(): void
{
Blade::anonymousComponentPath(__DIR__.'/../components');
}
当没有指定前缀时,注册的组件路径可以在你的 Blade 组件中直接渲染,而无需添加前缀。例如,如果在上述注册路径下存在 panel.blade.php
组件,可以像下面这样渲染:
<x-panel />
可以将前缀 "namespaces" 作为第二个参数提供给 anonymousComponentPath
方法:
Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
当提供前缀时,可以通过在渲染组件时将组件的命名空间前缀添加到组件名称中,来渲染该 "命名空间" 内的组件:
<x-dashboard::panel />
构建布局
使用组件构建布局
大多数 web 应用程序在各个页面之间保持相同的总体布局。如果我们在每个视图中都必须重复整个布局的 HTML,这将非常麻烦且难以维护。幸运的是,我们可以将这个布局定义为一个单独的 【Blade 组件】,并在整个应用程序中使用它。
定义布局组件
例如,假设我们正在构建一个 "todo" 列表应用程序。我们可以定义一个如下的 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>
请记住,注入到组件中的内容将被提供到我们布局组件中的默认 $slot
变量中。正如您可能已经注意到的,我们的布局还会尊重 $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
指令来附加(而不是覆盖)内容到布局的 sidebar 中。当视图被渲染时,@parent
指令会被布局中的内容替换。
与之前的例子不同,这个 |
@yield
指令也可以接受一个默认值作为第二个参数。如果被 @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
指令内部,您可以输出 $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 通过将它们写入 storage/framework/views
目录来渲染内联的 Blade 模板。如果您希望在渲染 Blade 模板后,Laravel 删除这些临时文件,可以向该方法提供 deleteCachedView
参数:
return Blade::render(
'Hello, {{ $name }}',
['name' => 'Julian Bashir'],
deleteCachedView: true
);
渲染 Blade 片段
在使用前端框架如 Turbo 和 htmx 时,您可能需要在 HTTP 响应中仅返回 Blade 模板的一部分。Blade “片段” 允许您实现这一点。开始时,您可以将 Blade 模板的一部分放在 @fragment
和 @endfragment
指令中:
@fragment('user-list')
<ul>
@foreach ($users as $user)
<li>{{ $user->name }}</li>
@endforeach
</ul>
@endfragment
然后,在渲染使用此模板的视图时,您可以调用 fragment
方法来指定仅返回指定的片段:
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
{
// ...
}
/**
* 启动任何应用程序服务。
*/
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 视图。可以使用 |
自定义 Echo 处理器
如果您尝试在 Blade 中 “回显” 一个对象,PHP 会调用该对象的 __toString
方法。__toString
是 PHP 内置的 “魔术方法” 之一。然而,有时候您无法控制给定类的 __toString
方法,例如当您与第三方库中的类进行交互时。
在这种情况下,Blade 允许您为特定类型的对象注册自定义的回显处理器。要实现这一点,您应该调用 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');
});
}
一旦定义了自定义的回显处理器,您就可以在 Blade 模板中直接回显该对象:
Cost: {{ $money }}
自定义 If 语句
编写自定义指令有时会比定义简单的自定义条件语句更复杂。因此,Blade 提供了一个 Blade::if
方法,允许您使用闭包快速定义自定义条件指令。例如,假设我们要定义一个自定义条件,检查应用程序的默认 "磁盘" 配置。我们可以在 AppServiceProvider
的 boot
方法中实现这一点:
use Illuminate\Support\Facades\Blade;
/**
* 启动任何应用程序服务。
*/
public function boot(): void
{
Blade::if('disk', function (string $value) {
return config('filesystems.default') === $value;
});
}
定义了自定义条件之后,您可以在模板中使用它:
@disk('local')
<!-- 应用程序正在使用本地磁盘... -->
@elsedisk('s3')
<!-- 应用程序正在使用 S3 磁盘... -->
@else
<!-- 应用程序正在使用其它磁盘... -->
@enddisk
@unlessdisk('local')
<!-- 应用程序没有使用本地磁盘... -->
@enddisk