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

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

您不仅可以显示传递给视图的变量的内容。您还可以输出任何 PHP 函数的结果。实际上,您可以在任何 Blade 输出语句中放置任何 PHP 代码:

当前UNIX时间戳是{{ time() }}。

HTML实体编码

默认情况下,Blade(和 Laravel e helper)将双重编码 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 攻击。如果您不希望数据被转义,可以使用以下语法:

你好,{!! $name !!}

在输出应用程序用户提供的内容时,请非常小心。通常情况下,应使用转义的双花括号语法来防止 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>

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

@verbatim 指令

如果在模板中大部分都是显示 JavaScript 变量,您可以使用 @verbatim 指令将 HTML 包装起来,这样您就不必在每个 Blade echo 语句前面添加 @ 符号了:

@verbatim
    <div class="container">
        Hello, {{ name }}.
    </div>
@endverbatim

Blade 指令

除了模板继承和数据显示之外,Blade 还提供了方便的快捷方式来处理常见的 PHP 控制结构,例如条件语句和循环。这些快捷方式提供了一种非常简洁明了的方式来处理 PHP 控制结构,同时又与其 PHP 对应项非常相似。

条件语句

您可以使用 @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
section 指令

你可以使用 @hasSection 指令来确定模板继承中的某个 section 是否存在内容:

@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>
 
    <div class="clearfix"></div>
@endif

你可以使用 sectionMissing 指令来判断一个 section 是否没有内容:

@sectionMissing('navigation')
    <div class="pull-right">
        @include('default-navigation')
    </div>
@endif

switch 语句

Switch 语句可以使用 @switch@case@break@default@endswitch 指令来构建:

@switch($i)
    @case(1)
        第一个 case ...
        @break
 
    @case(2)
        第二个 case ...
        @break
 
    @default
        默认 case ...
@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

在通过 foreach 循环迭代时,您可以使用loop变量获取有关循环的有价值信息,例如您是否在循环的第一个或最后一个迭代中。

使用循环时,您还可以使用 @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 选项是否应该“选定”:

<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()) />

包含子视图

虽然你可以使用 @include 指令来包含一个 Blade 视图,但是 Blade 组件提供了类似的功能,并且提供了诸如数据和属性绑定之类的优点。

Blade 的 @include 指令允许你从另一个视图中包含一个 Blade 视图。所有在父视图中可用的变量将可用于被包含的视图中:

<div>
    @include('shared.errors')
 
    <form>
        <!-- Form Contents -->
    </form>
</div>

即使被包含的视图将继承父视图中的所有数据,你也可以传递一个附加数据的数组,这些数据应该在被包含的视图中可用:

@include('view.name', ['status' => 'complete'])

如果你尝试包含一个不存在的视图,Laravel 将抛出一个错误。如果你想要包含一个可能存在也可能不存在的视图,你应该使用 @includeIf 指令:

@includeIf('view.name', ['status' => 'complete'])

如果你想要在给定的布尔表达式为 truefalse 时包含一个视图,你可以使用 @includeWhen@includeUnless 指令:

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

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

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

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

通过集合渲染视图

Blade 提供了 @each 指令,您可以将循环和包含结合到一行中:

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

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

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

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

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

@once指令

@once 指令允许您定义模板的一个部分,每个渲染周期只评估一次。这对于使用 stacks 将特定的 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 语句,可以将语句包含在 @php 指令中:

@php($counter = 1)

注释

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

{{-- 这个注释不会在渲染的 HTML 中存在 --}}

组件

组件和插槽提供了类似于 sections、layouts 和 includes 的功能,但有些人可能会发现组件和插槽的思维模型更容易理解。编写组件有两种方法:基于类的组件和匿名组件。

要创建一个基于类的组件,可以使用 make:component Artisan 命令。为了说明如何使用组件,我们将创建一个简单的 Alert 组件。make:component 命令将把组件放在 app/View/Components 目录中:

php artisan make:component Alert

make:component 命令还将为组件创建一个视图模板。视图将被放置在 resources/views/components 目录中。在为自己的应用程序编写组件时,组件会自动在 app/View/Components 目录和 resources/views/components 目录中发现,因此通常不需要进行进一步的组件注册。

您还可以在子目录中创建组件:

php artisan make:component Forms/Input

上面的命令将在 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');
}

这将允许使用vendor命名空间使用 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;
 
/**
 * Whether the component should be rendered
 */
public function shouldRender(): bool
{
    return Str::length($this->message) > 0;
}

向组件传递数据

您可以使用 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>

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 方法中返回一个闭包。该闭包将接收一个 $data 数组作为其唯一参数。该数组将包含提供有关组件的几个元素的信息:

use Closure;
 
/**
 * Get the view / contents that represent the component.
 */
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 将是 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>

在组件标签中使用 @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>
条件合并类

有时,如果某个给定条件成立,你可能希望合并类。你可以通过 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 方法的值将被视为该属性的“默认”值。但与 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

您可以使用 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 组件,以允许注入“标题”插槽:

<!-- /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 框架,您可能会熟悉“作用域插槽”,它允许您在插槽中访问组件中的数据或方法。您可以通过在组件上定义公共方法或属性并通过 $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 方法中返回组件的标记:

/**
 * 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 组件,根据运行时值或变量呈现组件:

<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;
 
/**
 * 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');
}

这将允许使用 vendor 命名空间使用 package-name:: 语法使用包组件:

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

Blade 将通过 pascal-casing 化组件名称自动检测与此组件关联的类。子目录也支持使用“点”符号。

匿名组件

类似于内联组件,匿名组件提供了一种通过单个文件来管理组件的机制。然而,匿名组件使用单个视图文件并没有关联的类。要定义一个匿名组件,你只需要在你的 resources/views/components 目录下放置一个 Blade 模板。例如,假设你在 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 允许在组件的模板目录中放置一个 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 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>

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

匿名组件路径

如前所述,通常通过将 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)
        {{ $task }}
    @endforeach
</x-layout>

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

<!-- 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应用程序在不同页面中保持相同的一般布局,因此将此布局定义为单个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 指令将被布局的内容替换。

与前面的示例相反,sidebar 侧边栏部分以 @endsection 结尾,而不是 @show@endsection 指令仅定义一个部分,而 @show 指令将定义并立即显示该部分。

@yield 指令还可以接受一个默认值作为其第二个参数。如果被渲染的部分未定义,则将呈现此值:

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

表单

CSRF 字段

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

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

方法字段

由于 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 门面提供的 render 方法来实现这一点。render 方法接受 Blade 模板字符串和可选的数据数组,以提供给模板:

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

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

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

Blade 模板碎片渲染

当使用 Turbohtmx 等前端框架时,您可能需要仅在 HTTP 响应中返回 Blade 模板的某一部分。Blade "fragments"允许您这样做。首先,将 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(应该是 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'); ?>

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

自定义 Echo 处理器

如果您使用 Blade 来输出一个对象,那么这个对象的 __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 模板中简单地输出该对象:

Cost: {{ $money }}

自定义 If 语句

Blade 的自定义指令对于定义简单的自定义条件语句来说有些过于复杂。因此,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

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 视图

>> 下一篇: 资源打包(Vite)