Skip to content

Blade 模板

介绍

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

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

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

想要将您的 Blade 模板提升到一个新的水平,并轻松构建动态界面?请查看 Laravel Livewire

显示数据

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

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

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

php
Hello, {{ $name }}.
lightbulb

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

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

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

HTML 实体编码

默认情况下,Blade(以及 Laravel 的 e 助手)会对 HTML 实体进行双重编码。如果您想禁用双重编码,请从 AppServiceProviderboot 方法中调用 Blade::withoutDoubleEncoding 方法:

php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        Blade::withoutDoubleEncoding();
    }
}

显示未转义的数据

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

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

在 echo 由您的应用程序用户提供的内容时要非常小心。通常,您应该使用转义的双大括号语法来防止在显示用户提供的数据时发生 XSS 攻击。

Blade 与 JavaScript 框架

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

php
<h1>Laravel</h1>

Hello, @{{ name }}.

在此示例中,Blade 将删除 @ 符号;然而,{{ name }} 表达式将保持不变,以便由您的 JavaScript 框架渲染。

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

php
{{-- Blade 模板 --}}
@@if()

<!-- HTML 输出 -->
@if()

渲染 JSON

有时您可能会将数组传递给视图,目的是将其渲染为 JSON 以初始化 JavaScript 变量。例如:

php
<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 对象:

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

最新版本的 Laravel 应用程序骨架包括一个 Js facade,它在您的 Blade 模板中提供了对该功能的便捷访问:

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

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

@verbatim 指令

如果您在模板的大部分中显示 JavaScript 变量,您可以将 HTML 包裹在 @verbatim 指令中,这样您就不必在每个 Blade echo 语句前加上 @ 符号:

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

Blade 指令

除了模板继承和显示数据,Blade 还为常见的 PHP 控制结构提供了便捷的快捷方式,例如条件语句和循环。这些快捷方式提供了一种非常简洁的方式来处理 PHP 控制结构,同时也保持了与其 PHP 对应物的熟悉性。

If 语句

您可以使用 @if@elseif@else@endif 指令构建 if 语句。这些指令的功能与其 PHP 对应物完全相同:

php
@if (count($records) === 1)
    我有一条记录!
@elseif (count($records) > 1)
    我有多条记录!
@else
    我没有任何记录!
@endif

为了方便起见,Blade 还提供了一个 @unless 指令:

php
@unless (Auth::check())
    您未登录。
@endunless

除了已经讨论过的条件指令外,@isset@empty 指令可以作为其各自 PHP 函数的便捷快捷方式使用:

php
@isset($records)
    // $records 已定义且不为 null...
@endisset

@empty($records)
    // $records 是“空的”...
@endempty

认证指令

@auth@guest 指令可以快速确定当前用户是否已认证或是访客:

php
@auth
    // 用户已认证...
@endauth

@guest
    // 用户未认证...
@endguest

如果需要,您可以指定在使用 @auth@guest 指令时应检查的认证守卫:

php
@auth('admin')
    // 用户已认证...
@endauth

@guest('admin')
    // 用户未认证...
@endguest

环境指令

您可以使用 @production 指令检查应用程序是否在生产环境中运行:

php
@production
    // 生产特定内容...
@endproduction

或者,您可以使用 @env 指令确定应用程序是否在特定环境中运行:

php
@env('staging')
    // 应用程序在“staging”中运行...
@endenv

@env(['staging', 'production'])
    // 应用程序在“staging”或“production”中运行...
@endenv

节指令

您可以使用 @hasSection 指令确定模板继承节是否有内容:

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

    <div class="clearfix"></div>
@endif

您可以使用 sectionMissing 指令来确定节是否没有内容:

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

Switch 语句

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

php
@switch($i)
    @case(1)
        第一种情况...
        @break

    @case(2)
        第二种情况...
        @break

    @default
        默认情况...
@endswitch

循环

除了条件语句,Blade 还为处理 PHP 的循环结构提供了简单的指令。同样,这些指令的功能与其 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
lightbulb

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

在使用循环时,您还可以使用 @continue@break 指令结束循环或跳过当前迭代:

php
@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach

您还可以在指令声明中包含继续或中断条件:

php
@foreach ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach

循环变量

在遍历 foreach 循环时,循环内将有一个 $loop 变量可用。此变量提供了一些有用的信息,例如当前循环索引以及这是否是循环的第一次或最后一次迭代:

php
@foreach ($users as $user)
    @if ($loop->first)
        这是第一次迭代。
    @endif

    @if ($loop->last)
        这是最后一次迭代。
    @endif

    <p>这是用户 {{ $user->id }}</p>
@endforeach

如果您在嵌套循环中,您可以通过 parent 属性访问父循环的 $loop 变量:

php
@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            这是父循环的第一次迭代。
        @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
@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>

包含子视图

lightbulb

虽然您可以自由使用 @include 指令,但 Blade 组件 提供了类似的功能,并且比 @include 指令提供了几个好处,例如数据和属性绑定。

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

html
<div>
    @include('shared.errors')

    <form>
        <!-- 表单内容 -->
    </form>
</div>

即使包含的视图将继承父视图中可用的所有数据,您也可以传递一个额外数据的数组,这些数据应可用于包含的视图:

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

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

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

如果您希望在给定布尔表达式计算为 truefalse@include 一个视图,您可以使用 @includeWhen@includeUnless 指令:

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

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

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

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

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

为集合渲染视图

您可以将循环和包含合并为一行,使用 Blade 的 @each 指令:

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

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

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

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

通过 @each 渲染的视图不会继承父视图中的变量。如果子视图需要这些变量,您应该使用 @foreach@include 指令。

@once 指令

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

php
@once
    @push('scripts')
        <script>
            // 您的自定义 JavaScript...
        </script>
    @endpush
@endonce

原生 PHP

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

php
@php
    $counter = 1;
@endphp

注释

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

php
{{-- 此注释不会出现在渲染的 HTML --}}

组件

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

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

php
php artisan make:component Alert

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

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

php
php artisan make:component Forms/Input

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

手动注册包组件

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

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

php
use Illuminate\Support\Facades\Blade;

/**
 * 启动包的服务。
 */
public function boot()
{
    Blade::component('package-alert', Alert::class);
}

一旦您的组件注册完毕,您可以使用其标签别名渲染它:

php
<x-package-alert/>

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

php
use Illuminate\Support\Facades\Blade;

/**
 * 启动包的服务。
 *
 * @return void
 */
public function boot()
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

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

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

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

渲染组件

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

php
<x-alert/>

<x-user-profile/>

如果组件类嵌套在 app/View/Components 目录中更深的地方,您可以使用 . 字符来指示目录嵌套。例如,如果我们假设一个组件位于 app/View/Components/Inputs/Button.php,我们可以像这样渲染它:

php
<x-inputs.button/>

传递数据到组件

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

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

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

php
<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * 警报类型。
     *
     * @var string
     */
    public $type;

    /**
     * 警报消息。
     *
     * @var string
     */
    public $message;

    /**
     * 创建组件实例。
     *
     * @param  string  $type
     * @param  string  $message
     * @return void
     */
    public function __construct($type, $message)
    {
        $this->type = $type;
        $this->message = $message;
    }

    /**
     * 获取表示组件的视图/内容。
     *
     * @return \Illuminate\View\View|\Closure|string
     */
    public function render()
    {
        return view('components.alert');
    }
}

当您的组件被渲染时,您可以通过名称 echo 组件的公共变量来显示组件的内容:

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

大小写

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

php
/**
 * 创建组件实例。
 *
 * @param  string  $alertType
 * @return void
 */
public function __construct($alertType)
{
    $this->alertType = $alertType;
}

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

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

转义属性渲染

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

php
<x-button ::class="{ danger: isDeleting }">
    提交
</x-button>

Blade 将渲染以下 HTML:

php
<button :class="{ danger: isDeleting }">
    提交
</button>

组件方法

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

php
/**
 * 确定给定选项是否为当前选定的选项。
 *
 * @param  string  $option
 * @return bool
 */
public function isSelected($option)
{
    return $option === $this->selected;
}

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

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

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

Blade 组件还允许您在类的 render 方法中访问组件名称、属性和插槽。然而,为了访问这些数据,您应该从组件的 render 方法返回一个闭包。闭包将接收一个 $data 数组作为其唯一参数。此数组将包含几个提供有关组件的信息的元素:

php
/**
 * 获取表示组件的视图/内容。
 *
 * @return \Illuminate\View\View|\Closure|string
 */
public function render()
{
    return function (array $data) {
        // $data['componentName'];
        // $data['attributes'];
        // $data['slot'];

        return '<div>组件内容</div>';
    };
}

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

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

额外的依赖项

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

php
use App\Services\AlertCreator

/**
 * 创建组件实例。
 *
 * @param  \App\Services\AlertCreator  $creator
 * @param  string  $type
 * @param  string  $message
 * @return void
 */
public function __construct(AlertCreator $creator, $type, $message)
{
    $this->creator = $creator;
    $this->type = $type;
    $this->message = $message;
}

隐藏属性/方法

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

php
<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * 警报类型。
     *
     * @var string
     */
    public $type;

    /**
     * 不应暴露给组件模板的属性/方法。
     *
     * @var array
     */
    protected $except = ['type'];
}

组件属性

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

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

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

php
<div {{ $attributes }}>
    <!-- 组件内容 -->
</div>
exclamation

目前不支持在组件标签中使用诸如 @env 之类的指令。例如,<x-alert :live="@env('production')"/> 将不会被编译。

默认/合并属性

有时您可能需要为属性指定默认值或将其他值合并到组件的某些属性中。为此,您可以使用属性包的 merge 方法。此方法对于定义一组应始终应用于组件的默认 CSS 类特别有用:

php
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>

如果我们假设这个组件是这样使用的:

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

组件的最终渲染 HTML 将如下所示:

html
<div class="alert alert-error mb-4">
    <!-- $message 变量的内容 -->
</div>

有条件地合并类

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

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

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

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

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

非类属性合并

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

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

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

php
<x-button type="submit">
    提交
</x-button>

在此示例中,button 组件的渲染 HTML 将是:

php
<button type="submit">
    提交
</button>

如果您希望某个属性(而不是 class)的默认值和注入值连接在一起,可以使用 prepends 方法。在此示例中,data-controller 属性将始终以 profile-controller 开头,任何其他注入的 data-controller 值将放在此默认值之后:

php
<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
    {{ $slot }}
</div>

检索和过滤属性

您可以使用 filter 方法过滤属性。此方法接受一个闭包,如果您希望在属性包中保留该属性,则应返回 true

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

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

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

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

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

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

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

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

php
@if ($attributes->has('class'))
    <div>Class 属性存在</div>
@endif

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

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

保留关键字

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

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

插槽

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

html
<!-- /resources/views/components/alert.blade.php -->

<div class="alert alert-danger">
    {{ $slot }}
</div>

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

html
<x-alert>
    <strong>哎呀!</strong> 出了点问题!
</x-alert>

有时组件可能需要在组件的不同位置渲染多个不同的插槽。让我们修改我们的警报组件以允许注入“标题”插槽:

html
<!-- /resources/views/components/alert.blade.php -->

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    {{ $slot }}
</div>

您可以使用 x-slot 标签定义命名插槽的内容。任何不在显式 x-slot 标签中的内容都将传递给组件中的 $slot 变量:

html
<x-alert>
    <x-slot name="title">
        服务器错误
    </x-slot>

    <strong>哎呀!</strong> 出了点问题!
</x-alert>

作用域插槽

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

html
<x-alert>
    <x-slot name="title">
        {{ $component->formatAlert('服务器错误') }}
    </x-slot>

    <strong>哎呀!</strong> 出了点问题!
</x-alert>

插槽属性

与 Blade 组件一样,您可以为插槽分配额外的属性,例如 CSS 类名:

html
<x-card class="shadow-sm">
    <x-slot name="heading" class="font-bold">
        标题
    </x-slot>

    内容

    <x-slot name="footer" class="text-sm">
        页脚
    </x-slot>
</x-card>

要与插槽属性交互,您可以访问插槽变量的 attributes 属性。有关如何与属性交互的更多信息,请查阅 组件属性 文档:

php
@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 方法返回组件的标记:

php
/**
 * 获取表示组件的视图/内容。
 *
 * @return \Illuminate\View\View|\Closure|string
 */
public function render()
{
    return <<<'blade'
        <div class="alert alert-danger">
            {{ $slot }}
        </div>
    blade;
}

生成内联视图组件

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

php
php artisan make:component Alert --inline

匿名组件

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

php
<x-alert/>

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

php
<x-inputs.button/>

匿名索引组件

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

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

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

html
<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 指令指定哪些属性应视为数据变量。组件上的所有其他属性将通过组件的属性包可用。如果您希望为数据变量提供默认值,可以将变量名称指定为数组键,将默认值指定为数组值:

php
<!-- /resources/views/components/alert.blade.php -->

@props(['type' => 'info', 'message'])

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>

给定上面的组件定义,我们可以像这样渲染组件:

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

访问父数据

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

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

<x-menu> 组件可能具有如下实现:

php
<!-- /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> 中也使其可用:

php
<!-- /resources/views/components/menu/item.blade.php -->

@aware(['color' => 'gray'])

<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
    {{ $slot }}
</li>

动态组件

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

php
<x-dynamic-component :component="$componentName" class="mt-4" />

手动注册组件

exclamation

以下关于手动注册组件的文档主要适用于那些编写包含视图组件的 Laravel 包的人。如果您没有编写包,则此部分组件文档可能与您无关。

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

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

php
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

/**
 * 启动包的服务。
 *
 * @return void
 */
public function boot()
{
    Blade::component('package-alert', AlertComponent::class);
}

一旦您的组件注册完毕,您可以使用其标签别名渲染它:

php
<x-package-alert/>

自动加载包组件

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

php
use Illuminate\Support\Facades\Blade;

/**
 * 启动包的服务。
 *
 * @return void
 */
public function boot()
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

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

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

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

构建布局

使用组件的布局

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

定义布局组件

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

html
<!-- resources/views/components/layout.blade.php -->

<html>
    <head>
        <title>{{ $title ?? 'Todo Manager' }}</title>
    </head>
    <body>
        <h1>待办事项</h1>
        <hr/>
        {{ $slot }}
    </body>
</html>

应用布局组件

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

html
<!-- resources/views/tasks.blade.php -->

<x-layout>
    @foreach ($tasks as $task)
        {{ $task }}
    @endforeach
</x-layout>

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

html
<!-- resources/views/tasks.blade.php -->

<x-layout>
    <x-slot name="title">
        自定义标题
    </x-slot>

    @foreach ($tasks as $task)
        {{ $task }}
    @endforeach
</x-layout>

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

php
use App\Models\Task;

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

使用模板继承的布局

定义布局

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

首先,让我们看一个简单的示例。首先,我们将检查一个页面布局。由于大多数 Web 应用程序在各种页面上保持相同的通用布局,因此将此布局定义为单个 Blade 视图是很方便的:

html
<!-- resources/views/layouts/app.blade.php -->

<html>
    <head>
        <title>应用名称 - @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 显示:

html
<!-- resources/views/child.blade.php -->

@extends('layouts.app')

@section('title', '页面标题')

@section('sidebar')
    @@parent

    <p>这将附加到主侧边栏。</p>
@endsection

@section('content')
    <p>这是我的正文内容。</p>
@endsection

在此示例中,sidebar 节使用 @@parent 指令将内容附加(而不是覆盖)到布局的侧边栏。@@parent 指令将在视图渲染时被布局的内容替换。

lightbulb

与前面的示例相反,此 sidebar 节以 @endsection 结束,而不是 @show@endsection 指令将仅定义一个节,而 @show 将定义并立即显示该节。

@yield 指令还接受一个默认值作为其第二个参数。如果未定义要显示的节,则将渲染此值:

php
@yield('content', '默认内容')

表单

CSRF 字段

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

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

    ...
</form>

方法字段

由于 HTML 表单无法发出 PUTPATCHDELETE 请求,您需要添加一个隐藏的 _method 字段来伪装这些 HTTP 动词。@method Blade 指令可以为您创建此字段:

html
<form action="/foo/bar" method="POST">
    @method('PUT')

    ...
</form>

验证错误

@error 指令可用于快速检查给定属性是否存在 验证错误消息。在 @error 指令中,您可以 echo $message 变量以显示错误消息:

html
<!-- /resources/views/post/create.blade.php -->

<label for="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 指令在属性没有错误时渲染内容:

html
<!-- /resources/views/auth.blade.php -->

<label for="email">电子邮件地址</label>

<input id="email" type="email" class="@error('email') is-invalid @else is-valid @enderror">

您可以将 特定错误包的名称 作为第二个参数传递给 @error 指令,以在包含多个表单的页面上检索验证错误消息:

html
<!-- /resources/views/auth.blade.php -->

<label for="email">电子邮件地址</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 库时特别有用:

html
@push('scripts')
    <script src="/example.js"></script>
@endpush

您可以根据需要多次推送到堆栈。要渲染完整的堆栈内容,请将堆栈的名称传递给 @stack 指令:

html
<head>
    <!-- 头部内容 -->

    @stack('scripts')
</head>

如果您希望将内容添加到堆栈的开头,您应该使用 @prepend 指令:

html
@push('scripts')
    这将是第二个...
@endpush

// 稍后...

@prepend('scripts')
    这将是第一个...
@endprepend

服务注入

@inject 指令可用于从 Laravel 服务容器 中检索服务。传递给 @inject 的第一个参数是服务将放入的变量的名称,而第二个参数是您希望解析的服务的类或接口名称:

html
@inject('metrics', 'App\Services\MetricsService')

<div>
    月收入:{{ $metrics->monthlyRevenue() }}。
</div>

扩展 Blade

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

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

php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册任何应用程序服务。
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        Blade::directive('datetime', function ($expression) {
            return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
        });
    }
}

如您所见,我们将在传递给指令的任何表达式上链接 format 方法。因此,在此示例中,此指令生成的最终 PHP 将是:

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

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

自定义 Echo 处理器

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

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

php
use Illuminate\Support\Facades\Blade;
use Money\Money;

/**
 * 启动任何应用程序服务。
 *
 * @return void
 */
public function boot()
{
    Blade::stringable(function (Money $money) {
        return $money->formatTo('en_GB');
    });
}

一旦定义了自定义 echo 处理器,您可以简单地在 Blade 模板中 echo 对象:

html
成本:{{ $money }}

自定义 If 语句

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

php
use Illuminate\Support\Facades\Blade;

/**
 * 启动任何应用程序服务。
 *
 * @return void
 */
public function boot()
{
    Blade::if('disk', function ($value) {
        return config('filesystems.default') === $value;
    });
}

一旦定义了自定义条件,您可以在模板中使用它:

html
@disk('local')
    <!-- 应用程序正在使用本地磁盘... -->
@elsedisk('s3')
    <!-- 应用程序正在使用 s3 磁盘... -->
@else
    <!-- 应用程序正在使用其他磁盘... -->
@enddisk

@unlessdisk('local')
    <!-- 应用程序未使用本地磁盘... -->
@enddisk