
# Blade 模板

-   [简介](#introduction)
    -   [使用 Livewire 增强 Blade](#supercharging-blade-with-livewire)
-   [显示数据](#displaying-data)
    -   [HTML 实体编码](#html-entity-encoding)
    -   [Blade 与 JavaScript 框架](#blade-and-javascript-frameworks)
-   [Blade 指令](#blade-directives)
    -   [If 语句](#if-statements)
    -   [Switch 语句](#switch-statements)
    -   [循环](#loops)
    -   [循环变量](#the-loop-variable)
    -   [条件类](#conditional-classes)
    -   [附加属性](#additional-attributes)
    -   [引入子视图](#including-subviews)
    -   [`@once` 指令](#the-once-directive)
    -   [原生 PHP](#raw-php)
    -   [注释](#comments)
-   [组件](#components)
    -   [渲染组件](#rendering-components)
    -   [索引组件](#index-components)
    -   [向组件传递数据](#passing-data-to-components)
    -   [组件属性](#component-attributes)
    -   [保留关键字](#reserved-keywords)
    -   [插槽](#slots)
    -   [内联组件视图](#inline-component-views)
    -   [动态组件](#dynamic-components)
    -   [手动注册组件](#manually-registering-components)
-   [匿名组件](#anonymous-components)
    -   [匿名索引组件](#anonymous-index-components)
    -   [数据属性 / 属性](#data-properties-attributes)
    -   [访问父级数据](#accessing-parent-data)
    -   [匿名组件路径](#anonymous-component-paths)
-   [构建布局](#building-layouts)
    -   [使用组件构建布局](#layouts-using-components)
    -   [使用模板继承构建布局](#layouts-using-template-inheritance)
-   [表单](#forms)
    -   [CSRF 字段](#csrf-field)
    -   [方法字段](#method-field)
    -   [验证错误](#validation-errors)
-   [堆栈](#stacks)
-   [服务注入](#service-injection)
-   [渲染内联 Blade 模板](#rendering-inline-blade-templates)
-   [渲染 Blade 片段](#rendering-blade-fragments)
-   [扩展 Blade](#extending-blade)
    -   [自定义 Echo 处理器](#custom-echo-handlers)
    -   [自定义 If 语句](#custom-if-statements)

## 简介

Blade 是 Laravel 内置的一个简单但功能强大的模板引擎。与一些 PHP 模板引擎不同，Blade 不限制您在模板中使用原生 PHP 代码。事实上，所有 Blade 模板都会被编译为原生 PHP 代码，并在被修改之前进行缓存，这意味着 Blade 几乎不会给您的应用程序增加额外开销。Blade 模板文件使用 `.blade.php` 文件扩展名，通常存储在 `resources/views` 目录中。



Blade 视图可以通过全局 `view` 辅助函数从路由或控制器中返回。当然，正如[视图](/docs/laravel/13.x/views)文档中所提到的，可以使用 `view` 辅助函数的第二个参数向 Blade 视图传递数据：

```php
Route::get('/', function () {
    return view('greeting', ['name' => 'Finn']);
});
```

### 使用 Livewire 增强 Blade

想让您的 Blade 模板更上一层楼，并轻松构建动态界面吗？请查看 [Laravel Livewire](https://livewire.laravel.com)。Livewire 允许您编写具有动态功能增强的 Blade 组件，这些功能通常只有通过 React、Svelte 或 Vue 等前端框架才能实现。它提供了一种非常好的方式，可以在不依赖许多 JavaScript 框架所带来的复杂性、客户端渲染或构建步骤的情况下，构建现代化的响应式前端。

## 显示数据

您可以通过将变量包裹在花括号中来显示传递给 Blade 视图的数据。例如，给定以下路由：

```php
Route::get('/', function () {
    return view('welcome', ['name' => 'Samantha']);
});
```

您可以像这样显示 `name` 变量的内容：

```blade
Hello, {{ $name }}.
```

> [!注意]
> Blade 的 `{{ }}` 输出语句会自动通过 PHP 的 `htmlspecialchars` 函数处理，以防止 XSS 攻击。


您不仅限于显示传递给视图的变量内容。您还可以输出任何 PHP 函数的结果。实际上，您可以在 Blade 输出语句中放入任意您想使用的 PHP 代码：

```blade
The current UNIX timestamp is {{ time() }}.
```



### HTML 实体编码

默认情况下，Blade（以及 Laravel 的 `e` 函数）会对 HTML 实体进行双重编码。如果您希望禁用双重编码，可以在 `AppServiceProvider` 的 `boot` 方法中调用 `Blade::withoutDoubleEncoding` 方法：

```php
<?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 攻击。如果您不希望数据被转义，可以使用以下语法：

```blade
Hello, {!! $name !!}.
```

> [!警告]
> 当输出由应用程序用户提供的内容时，请务必非常小心。在显示用户提供的数据时，通常应该使用经过转义的双花括号语法，以防止 XSS 攻击。

### Blade 与 JavaScript 框架

由于许多 JavaScript 框架也使用“花括号”来表示需要在浏览器中显示的表达式，因此您可以使用 `@` 符号来告知 Blade 渲染引擎某个表达式应保持原样，不进行处理。例如：

```blade
<h1>Laravel</h1>

Hello, @{{ name }}.
```

在这个示例中，`@` 符号会被 Blade 移除；但是，`{{ name }}` 表达式会保持不变，不会被 Blade 引擎处理，从而允许它被您的 JavaScript 框架进行渲染。

`@` 符号也可以用于转义 Blade 指令：

```blade
{{-- Blade template --}}
@@if()

<!-- HTML output -->
@if()
```

#### 渲染 JSON

有时您可能会向视图传递一个数组，并希望将其渲染为 JSON，以便初始化一个 JavaScript 变量。例如：

```php
<script>
    var app = <?php echo json_encode($array); ?>;
</script>
```



不过，与其手动调用 `json_encode`，您可以使用 `Illuminate\Support\Js::from` 方法。`from` 方法接受与 PHP 的 `json_encode` 函数相同的参数；但是，它会确保生成的 JSON 已经过正确转义，可以安全地包含在 HTML 引号中。`from` 方法会返回一个字符串形式的 `JSON.parse` JavaScript 语句，该语句会将给定的对象或数组转换为有效的 JavaScript 对象：

```blade
<script>
    var app = {{ Illuminate\Support\Js::from($array) }};
</script>
```

最新版本的 Laravel 应用程序骨架中包含一个 `Js` 门面，它可以在 Blade 模板中方便地访问此功能：

```blade
<script>
    var app = {{ Js::from($array) }};
</script>
```

> [!警告]
> 您应该只使用 `Js::from` 方法将现有变量渲染为 JSON。Blade 模板基于正则表达式实现，尝试将复杂表达式传递给该指令可能会导致意外错误。

#### `@verbatim` 指令

如果您需要在模板的大部分区域显示 JavaScript 变量，可以使用 `@verbatim` 指令包裹 HTML，这样您就不必在每个 Blade 输出语句前添加 `@` 符号：

```blade
@verbatim
    <div class="container">
        Hello, {{ name }}.
    </div>
@endverbatim
```

## Blade 指令

除了模板继承和显示数据之外，Blade 还为常见的 PHP 控制结构提供了便捷的快捷方式，例如条件语句和循环。这些快捷方式提供了一种非常简洁、紧凑的方式来使用 PHP 控制结构，同时仍然保持与对应 PHP 语法的一致性。



### If 语句

您可以使用 `@if`、`@elseif`、`@else` 和 `@endif` 指令构造 `if` 语句。这些指令与对应的 PHP 语法功能完全相同：

```blade
@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` 指令：

```blade
@unless (Auth::check())
    You are not signed in.
@endunless
```

除了前面介绍的条件指令之外，`@isset` 和 `@empty` 指令也可以作为对应 PHP 函数的便捷快捷方式使用：

```blade
@isset($records)
    // $records is defined and is not null...
@endisset

@empty($records)
    // $records is "empty"...
@endempty
```

#### 身份验证指令

`@auth` 和 `@guest` 指令可用于快速判断当前用户是否已经[通过身份验证](/docs/laravel/13.x/authentication)或是否为访客：

```blade
@auth
    // The user is authenticated...
@endauth

@guest
    // The user is not authenticated...
@endguest
```

如果需要，在使用 `@auth` 和 `@guest` 指令时，可以指定需要检查的身份验证守卫：

```blade
@auth('admin')
    // The user is authenticated...
@endauth

@guest('admin')
    // The user is not authenticated...
@endguest
```

#### 环境指令

您可以使用 `@production` 指令检查应用程序是否正在生产环境中运行：

```blade
@production
    // Production specific content...
@endproduction
```

或者，您可以使用 `@env` 指令判断应用程序是否正在特定环境中运行：

```blade
@env('staging')
    // The application is running in "staging"...
@endenv

@env(['staging', 'production'])
    // The application is running in "staging" or "production"...
@endenv
```



#### Section 指令

您可以使用 `@hasSection` 指令判断模板继承中的某个区块是否包含内容：

```blade
@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>

    <div class="clearfix"></div>
@endif
```

您可以使用 `sectionMissing` 指令判断某个区块是否没有内容：

```blade
@sectionMissing('navigation')
    <div class="pull-right">
        @include('default-navigation')
    </div>
@endif
```

#### Session 指令

`@session` 指令可用于判断某个 [session](/docs/laravel/13.x/session) 值是否存在。如果 session 值存在，则会执行 `@session` 和 `@endsession` 指令之间的模板内容。

在 `@session` 指令的内容中，您可以输出 `$value` 变量来显示 session 值：

```blade
@session('status')
    <div class="p-4 bg-green-100">
        {{ $value }}
    </div>
@endsession
```

#### Context 指令

`@context` 指令可用于判断某个 [context](/docs/laravel/13.x/context) 值是否存在。如果 context 值存在，则会执行 `@context` 和 `@endcontext` 指令之间的模板内容。

在 `@context` 指令的内容中，您可以输出 `$value` 变量来显示 context 值：

```blade
@context('canonical')
    <link href="{{ $value }}" rel="canonical">
@endcontext
```

### Switch 语句

可以使用 `@switch`、`@case`、`@break`、`@default` 和 `@endswitch` 指令构造 switch 语句：

```blade
@switch($i)
    @case(1)
        First case...
        @break

    @case(2)
        Second case...
        @break

    @default
        Default case...
@endswitch
```

### 循环

除了条件语句之外，Blade 还提供了用于处理 PHP 循环结构的简单指令。同样，每个指令的功能都与对应的 PHP 语法完全一致：

```blade
@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor

@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse

@while (true)
    <p>I'm looping forever.</p>
@endwhile
```

> [!注意]
> 在遍历 `foreach` 循环时，您可以使用[循环变量](#the-loop-variable)获取有关循环的有用信息，例如当前是否处于循环的第一次或最后一次迭代。



在使用循环时，您还可以使用 `@continue` 和 `@break` 指令跳过当前迭代或结束循环：

```blade
@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach
```

您也可以直接在指令声明中包含继续或中断条件：

```blade
@foreach ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach
```

### 循环变量

在遍历 `foreach` 循环时，循环内部会提供一个 `$loop` 变量。该变量可以访问一些有用的信息，例如当前循环索引，以及当前是否是循环的第一次或最后一次迭代：

```blade
@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` 变量：

```blade
@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` 变量还包含许多其他有用的属性：

<div class="overflow-auto">

| 属性 | 描述 |
| --- | --- |
| `$loop->index` | 当前循环迭代的索引（从 0 开始）。 |
| `$loop->iteration` | 当前循环迭代次数（从 1 开始）。 |
| `$loop->remaining` | 循环中剩余的迭代次数。 |
| `$loop->count` | 正在遍历的数组中的项目总数。 |
| `$loop->first` | 是否为循环中的第一次迭代。 |
| `$loop->last` | 是否为循环中的最后一次迭代。 |
| `$loop->even` | 是否为循环中的偶数次迭代。 |
| `$loop->odd` | 是否为循环中的奇数次迭代。 |
| `$loop->depth` | 当前循环的嵌套层级。 |
| `$loop->parent` | 在嵌套循环中，父级循环的变量。 |

</div>



### 条件类与样式

`@class` 指令会根据条件编译 CSS 类字符串。该指令接收一个类数组，其中数组键包含您希望添加的类，而值是一个布尔表达式。如果数组元素具有数字键，则该元素始终会被包含在最终渲染的类列表中：

```blade
@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 样式：

```blade
@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`：

```blade
<input
    type="checkbox"
    name="active"
    value="active"
    @checked(old('active', $user->active))
/>
```

同样，`@selected` 指令可以用于判断某个下拉选项是否应该处于“选中”状态：

```blade
<select name="version">
    @foreach ($product->versions as $version)
        <option value="{{ $version }}" @selected(old('version') == $version)>
            {{ $version }}
        </option>
    @endforeach
</select>
```

此外，`@disabled` 指令可以用于判断某个元素是否应该处于“禁用”状态：

```blade
<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
```

另外，`@readonly` 指令可以用于判断某个元素是否应该处于“只读”状态：

```blade
<input
    type="email"
    name="email"
    value="email@laravel.com"
    @readonly($user->isNotAdmin())
/>
```



此外，`@required` 指令可以用于判断某个元素是否应该处于“必填”状态：

```blade
<input
    type="text"
    name="title"
    value="title"
    @required($user->isAdmin())
/>
```

### 引入子视图

> [!注意]
> 虽然您可以自由使用 `@include` 指令，但 Blade [组件](#components) 提供了类似的功能，并且相比 `@include` 指令具有一些优势，例如数据和属性绑定。

Blade 的 `@include` 指令允许您在一个视图中引入另一个 Blade 视图。父视图中可用的所有变量也都会在被引入的视图中可用：

```blade
<div>
    @include('shared.errors')

    <form>
        <!-- Form Contents -->
    </form>
</div>
```

即使被引入的视图会继承父视图中的所有可用数据，您也可以传递一个额外的数据数组，使这些数据可以在被引入的视图中使用：

```blade
@include('view.name', ['status' => 'complete'])
```

如果您尝试 `@include` 一个不存在的视图，Laravel 将会抛出错误。如果您希望引入一个可能存在也可能不存在的视图，则应该使用 `@includeIf` 指令：

```blade
@includeIf('view.name', ['status' => 'complete'])
```

如果您希望在某个布尔表达式计算结果为 `true` 或 `false` 时引入视图，可以使用 `@includeWhen` 和 `@includeUnless` 指令：

```blade
@includeWhen($boolean, 'view.name', ['status' => 'complete'])

@includeUnless($boolean, 'view.name', ['status' => 'complete'])
```

如果您希望从给定的视图数组中引入第一个存在的视图，可以使用 `includeFirst` 指令：

```blade
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
```



如果你希望在包含视图时不继承父视图中的任何变量，可以使用 `@includeIsolated` 指令。被包含的视图只能访问你显式传递给它的变量：

```blade
@includeIsolated('view.name', ['user' => $user])
```

> [!警告]
> 你应该避免在 Blade 视图中使用 `__DIR__` 和 `__FILE__` 常量，因为它们将指向缓存并编译后的视图所在位置。

#### 为集合渲染视图

你可以使用 Blade 的 `@each` 指令，将循环和包含操作合并到一行中：

```blade
@each('view.name', $jobs, 'job')
```

`@each` 指令的第一个参数是为数组或集合中的每个元素渲染的视图。第二个参数是你希望遍历的数组或集合，而第三个参数是在视图中分配给当前迭代项的变量名。例如，如果你正在遍历一个 `jobs` 数组，通常会希望在视图中通过 `job` 变量访问每个任务。当前迭代项的数组键将在视图中通过 `key` 变量提供。

你还可以向 `@each` 指令传递第四个参数。该参数用于指定当给定数组为空时要渲染的视图。


```blade
@each('view.name', $jobs, 'job', 'view.empty')
```

> [!警告]
> 通过 `@each` 渲染的视图不会继承父视图中的变量。如果子视图需要这些变量，你应该改用 `@foreach` 和 `@include` 指令。



### `@once` 指令

`@once` 指令允许您定义模板中的一部分内容，使其在每次渲染周期中只会执行一次。当您需要使用[堆栈](#stacks)将某段 JavaScript 推送到页面头部时，这可能非常有用。

例如，如果您在循环中渲染某个[组件](#components)，您可能只希望在第一次渲染该组件时将 JavaScript 推送到页面头部：

```blade
@once
    @push('scripts')
        <script>
            // Your custom JavaScript...
        </script>
    @endpush
@endonce
```

由于 `@once` 指令通常与 `@push` 或 `@prepend` 指令一起使用，因此 Blade 提供了 `@pushOnce` 和 `@prependOnce` 指令以方便使用：

```blade
@pushOnce('scripts')
    <script>
        // Your custom JavaScript...
    </script>
@endPushOnce
```

如果您从两个不同的 Blade 模板中推送重复内容，则应该为 `@pushOnce` 指令提供一个唯一标识符作为第二个参数，以确保内容只渲染一次：

```blade
<!-- pie-chart.blade.php -->
@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce

<!-- line-chart.blade.php -->
@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce
```

### 原生 PHP

在某些情况下，将 PHP 代码嵌入到视图中非常有用。您可以使用 Blade 的 `@php` 指令在模板中执行一段原生 PHP 代码：

```blade
@php
    $counter = 1;
@endphp
```

或者，如果您只需要使用 PHP 导入一个类，可以使用 `@use` 指令：

```blade
@use('App\Models\Flight')
```

您可以为 `@use` 指令提供第二个参数，用于为导入的类设置别名：

```blade
@use('App\Models\Flight', 'FlightModel')
```



如果您在同一个命名空间中有多个类，可以将这些类的导入进行分组：

```blade
@use('App\Models\{Flight, Airport}')
```

`@use` 指令还支持通过在导入路径前添加 `function` 或 `const` 修饰符来导入 PHP 函数和常量：

```blade
@use(function App\Helpers\format_currency)
@use(const App\Constants\MAX_ATTEMPTS)
```

与类导入一样，函数和常量也支持别名：

```blade
@use(function App\Helpers\format_currency, 'formatMoney')
@use(const App\Constants\MAX_ATTEMPTS, 'MAX_TRIES')
```

分组导入同样支持 `function` 和 `const` 修饰符，这使您可以在一个指令中从同一个命名空间导入多个符号：

```blade
@use(function App\Helpers\{format_currency, format_date})
@use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT})
```

### 注释

Blade 还允许您在视图中定义注释。但是，与 HTML 注释不同，Blade 注释不会包含在应用程序返回的 HTML 中：

```blade
{{-- This comment will not be present in the rendered HTML --}}
```

## 组件

组件和插槽提供了与区块、布局以及包含文件类似的优势；但是，有些人可能会发现组件和插槽的思维模型更容易理解。编写组件有两种方式：基于类的组件和匿名组件。

要创建基于类的组件，可以使用 `make:component` Artisan 命令。为了演示如何使用组件，我们将创建一个简单的 `Alert` 组件。`make:component` 命令会将组件放置在 `app/View/Components` 目录中：

```shell
php artisan make:component Alert
```



`make:component` 命令还会为组件创建一个视图模板。该视图会被放置在 `resources/views/components` 目录中。在为自己的应用程序编写组件时，组件会自动在 `app/View/Components` 目录和 `resources/views/components` 目录中被发现，因此通常不需要进一步注册组件。

您也可以在子目录中创建组件：

```shell
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(): void
{
    Blade::component('package-alert', Alert::class);
}
```

组件注册完成后，可以使用其标签别名进行渲染：

```blade
<x-package-alert/>
```

或者，您可以使用 `componentNamespace` 方法按照约定自动加载组件类。例如，一个 `Nightshade` 扩展包可能包含 `Calendar` 和 `ColorPicker` 组件，它们位于 `Package\Views\Components` 命名空间中：

```php
use Illuminate\Support\Facades\Blade;

/**
 * 启动扩展包服务。
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
```



这将允许通过 `package-name::` 语法使用扩展包的组件命名空间：

```blade
<x-nightshade::calendar />
<x-nightshade::color-picker />
```

Blade 会通过将组件名称转换为 Pascal 命名格式，自动检测与该组件关联的类。子目录也支持使用“点”符号表示。

### 渲染组件

要显示一个组件，您可以在 Blade 模板中使用 Blade 组件标签。Blade 组件标签以字符串 `x-` 开头，后面跟随组件类名称的小写短横线（kebab case）形式：

```blade
<x-alert/>

<x-user-profile/>
```

如果组件类位于 `app/View/Components` 目录更深层的嵌套目录中，您可以使用 `.` 字符表示目录嵌套。例如，假设组件位于 `app/View/Components/Inputs/Button.php`，我们可以这样渲染它：

```blade
<x-inputs.button/>
```

如果您希望有条件地渲染组件，可以在组件类中定义一个 `shouldRender` 方法。如果 `shouldRender` 方法返回 `false`，则组件不会被渲染：

```php
use Illuminate\Support\Str;

/**
 * 判断组件是否应该被渲染
 */
public function shouldRender(): bool
{
    return Str::length($this->message) > 0;
}
```

### 索引组件

有时组件属于一个组件组，您可能希望将相关组件组织在同一个目录中。例如，假设有一个 “card” 组件，其类结构如下：

```text
App\Views\Components\Card\Card
App\Views\Components\Card\Header
App\Views\Components\Card\Body
```

由于根 `Card` 组件嵌套在 `Card` 目录中，您可能会认为需要通过 `<x-card.card>` 来渲染该组件。

但是，当组件的文件名与组件所在目录名称相同时，Laravel 会自动认为该组件是“根”组件，并允许您在渲染组件时省略重复的目录名称：

```blade
<x-card>
    <x-card.header>...</x-card.header>
    <x-card.body>...</x-card.body>
</x-card>
```



### 向组件传递数据

您可以通过 HTML 属性向 Blade 组件传递数据。硬编码的基础类型值可以使用简单的 HTML 属性字符串传递给组件。PHP 表达式和变量应该通过以 `:` 字符作为前缀的属性传递给组件：

```blade
<x-alert type="error" :message="$message"/>
```

您应该在组件类的构造函数中定义组件的所有数据属性。组件中的所有公共属性都会自动提供给组件视图使用。无需通过组件的 `render` 方法将数据传递给视图：

```php
<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
    /**
     * 创建组件实例。
     */
    public function __construct(
        public string $type,
        public string $message,
    ) {}

    /**
     * 获取表示该组件的视图 / 内容。
     */
    public function render(): View
    {
        return view('components.alert');
    }
}
```

当组件被渲染时，您可以通过输出组件公共变量的名称来显示这些变量的内容：

```blade
<div class="alert alert-{{ $type }}">
    {{ $message }}
</div>
```

#### 命名规则

组件构造函数参数应该使用 `camelCase`（驼峰命名法）形式定义，而在 HTML 属性中引用参数名称时应该使用 `kebab-case`（短横线命名法）。

例如，给定以下组件构造函数：

```php
/**
 * 创建组件实例。
 */
public function __construct(
    public string $alertType,
) {}
```

可以像这样向组件提供 `$alertType` 参数：

```blade
<x-alert alert-type="danger" />
```

#### 简短属性语法

在向组件传递属性时，您还可以使用“简短属性”语法。当属性名称经常与其对应的变量名称相同时，这种方式非常方便：


```blade
{{-- 简短属性语法... --}}
<x-profile :$userId :$name />

{{-- 等价于... --}}
<x-profile :user-id="$userId" :name="$name" />
```



#### 转义属性渲染

由于一些 JavaScript 框架（例如 Alpine.js）也使用以冒号开头的属性，因此您可以使用双冒号（`::`）前缀告诉 Blade 该属性不是 PHP 表达式。

例如，给定以下组件：

```blade
<x-button ::class="{ danger: isDeleting }">
    Submit
</x-button>
```

Blade 会渲染出以下 HTML：

```blade
<button :class="{ danger: isDeleting }">
    Submit
</button>
```

#### 组件方法

除了公共变量可以在组件模板中使用之外，组件中的任何公共方法也可以被调用。

例如，假设有一个组件包含一个 `isSelected` 方法：

```php
/**
 * 判断给定选项是否为当前选中的选项。
 */
public function isSelected(string $option): bool
{
    return $option === $this->selected;
}
```

您可以通过调用与方法名称匹配的变量，在组件模板中执行该方法：

```blade
<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
    {{ $label }}
</option>
```

#### 在组件类中访问属性和插槽

Blade 组件还允许您在类的 `render` 方法中访问组件名称、属性以及插槽。

但是，为了访问这些数据，您应该从组件的 `render` 方法中返回一个闭包：

```php
use Closure;

/**
 * 获取表示该组件的视图 / 内容。
 */
public function render(): Closure
{
    return function () {
        return '<div {{ $attributes }}>Components content</div>';
    };
}
```

组件 `render` 方法返回的闭包还可以接收一个 `$data` 数组作为唯一参数。该数组将包含多个用于提供组件信息的元素：

```php
return function (array $data) {
    // $data['componentName'];
    // $data['attributes'];
    // $data['slot'];

    return '<div {{ $attributes }}>Components content</div>';
}
```

> [!警告]
> `$data` 数组中的元素绝不能直接嵌入到 `render` 方法返回的 Blade 字符串中，因为这样可能会通过恶意属性内容导致远程代码执行。



`componentName` 等于 HTML 标签中 `x-` 前缀之后所使用的名称。因此，`<x-alert />` 的 `componentName` 将会是 `alert`。`attributes` 元素将包含 HTML 标签中存在的所有属性。`slot` 元素是一个 `Illuminate\Support\HtmlString` 实例，其中包含组件插槽的内容。 闭包应该返回一个字符串。如果返回的字符串对应一个现有视图，则会渲染该视图；否则，返回的字符串将会作为内联 Blade 视图进行解析。 #### 额外依赖 如果你的组件需要 Laravel [服务容器](/docs/laravel/13.x/container)中的依赖，可以在组件的数据属性之前列出它们，容器会自动注入这些依赖：

#### 额外依赖

如果你的组件需要 Laravel [服务容器](/docs/laravel/13.x/container)中的依赖，可以在组件的数据属性之前列出它们，容器会自动注入这些依赖：

```php
use App\Services\AlertCreator;

/**
 * 创建组件实例。
 */
public function __construct(
    public AlertCreator $creator,
    public string $type,
    public string $message,
) {}
```

#### 隐藏属性 / 方法

如果你希望阻止某些公共方法或属性作为变量暴露给组件模板，可以将它们添加到组件的 `$except` 数组属性中：

```php
<?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` 组件：

```blade
<x-alert type="error" :message="$message" class="mt-4"/>
```



所有不属于组件构造函数的属性都会自动添加到组件的“属性包”（attribute bag）中。该属性包会通过 `$attributes` 变量自动提供给组件。所有属性都可以通过输出该变量在组件中进行渲染：

```blade
<div {{ $attributes }}>
    <!-- 组件内容 -->
</div>
```

> [!警告]
> 当前不支持在组件标签中使用 `@env` 等指令。例如，`<x-alert :live="@env('production')"/>` 将不会被编译。

#### 默认 / 合并属性

有时你可能需要为属性指定默认值，或者将额外的值合并到组件的某些属性中。为此，你可以使用属性包的 `merge` 方法。该方法特别适用于定义一组始终应该应用于组件的默认 CSS 类：

```blade
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>
```

假设该组件以如下方式使用：

```blade
<x-alert type="error" :message="$message" class="mb-4"/>
```

组件最终渲染出的 HTML 将如下所示：

```blade
<div class="alert alert-error mb-4">
    <!-- $message 变量的内容 -->
</div>
```

#### 条件合并类

有时你可能希望在某个条件为 `true` 时合并类。你可以通过 `class` 方法实现这一点。该方法接收一个类数组，其中数组键包含你希望添加的类，而值是一个布尔表达式。如果数组元素具有数字键，则该元素始终会被包含在最终渲染的类列表中：

```blade
<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>
    {{ $message }}
</div>
```



如果你需要将其他属性合并到组件上，可以将 `merge` 方法链接到 `class` 方法之后：

```blade
<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>
    {{ $slot }}
</button>
```

> [!注意]
> 如果你需要在其他不应该接收合并属性的 HTML 元素上有条件地编译类，可以使用 [@class 指令](#conditional-classes)。

#### 非 Class 属性合并

当合并不是 `class` 属性的其他属性时，传递给 `merge` 方法的值将被视为该属性的“默认”值。但是，与 `class` 属性不同，这些属性不会与注入的属性值进行合并，而是会被覆盖。例如，一个 `button` 组件的实现可能如下所示：

```blade
<button {{ $attributes->merge(['type' => 'button']) }}>
    {{ $slot }}
</button>
```

要使用自定义的 `type` 渲染按钮组件，可以在使用组件时指定它。如果没有指定 `type`，则会使用 `button` 类型：

```blade
<x-button type="submit">
    Submit
</x-button>
```

在此示例中，`button` 组件渲染后的 HTML 将如下所示：

```blade
<button type="submit">
    Submit
</button>
```

如果你希望除 `class` 之外的某个属性能够将其默认值和注入值连接在一起，可以使用 `prepends` 方法。在此示例中，`data-controller` 属性始终会以 `profile-controller` 开头，而任何额外注入的 `data-controller` 值都会被放置在该默认值之后：

```blade
<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
    {{ $slot }}
</div>
```

#### 获取和过滤属性

你可以使用 `filter` 方法过滤属性。该方法接收一个闭包，如果你希望保留属性包中的某个属性，该闭包应该返回 `true`：

```blade
{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}
```



为了方便，你可以使用 `whereStartsWith` 方法来获取所有键以指定字符串开头的属性：

```blade
{{ $attributes->whereStartsWith('wire:model') }}
```

相反，`whereDoesntStartWith` 方法可以用于排除所有键以指定字符串开头的属性：

```blade
{{ $attributes->whereDoesntStartWith('wire:model') }}
```

使用 `first` 方法，你可以渲染给定属性包中的第一个属性：

```blade
{{ $attributes->whereStartsWith('wire:model')->first() }}
```

如果你想检查某个属性是否存在于组件上，可以使用 `has` 方法。该方法接收属性名称作为唯一参数，并返回一个布尔值，表示该属性是否存在：

```blade
@if ($attributes->has('class'))
    <div>Class 属性存在</div>
@endif
```

如果将数组传递给 `has` 方法，该方法将判断给定的所有属性是否都存在于组件上：

```blade
@if ($attributes->has(['name', 'class']))
    <div>所有属性都存在</div>
@endif
```

`hasAny` 方法可以用于判断给定的任意属性是否存在于组件上：

```blade
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
    <div>其中一个属性存在</div>
@endif
```

你可以使用 `get` 方法获取指定属性的值：

```blade
{{ $attributes->get('class') }}
```

`only` 方法可以用于仅获取具有指定键的属性：

```blade
{{ $attributes->only(['class']) }}
```

`except` 方法可以用于获取除指定键之外的所有属性：

```blade
{{ $attributes->except(['class']) }}
```



### 保留关键字

默认情况下，某些关键字会被保留用于 Blade 内部渲染组件时使用。以下关键字不能在你的组件中定义为公共属性或方法名称：



<div class="content-list" markdown="1">

- `data`
- `render`
- `resolve`
- `resolveView`
- `shouldRender`
- `view`
- `withAttributes`
- `withName`

</div>

### 插槽

你经常需要通过“插槽”（slots）向组件传递额外的内容。组件插槽通过输出 `$slot` 变量进行渲染。为了探索这个概念，我们假设一个 `alert` 组件具有如下标记：

```blade
<!-- /resources/views/components/alert.blade.php -->

<div class="alert alert-danger">
    {{ $slot }}
</div>
```

我们可以通过向组件中注入内容，将内容传递给 `slot`：

```blade
<x-alert>
    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```

有时，一个组件可能需要在组件内不同的位置渲染多个不同的插槽。让我们修改 `alert` 组件，使其支持注入一个 "title" 插槽：

```blade
<!-- /resources/views/components/alert.blade.php -->

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    {{ $slot }}
</div>
```

你可以使用 `x-slot` 标签定义命名插槽的内容。任何不在显式 `x-slot` 标签内的内容都会通过 `$slot` 变量传递给组件：

```xml
<x-alert>
    <x-slot:title>
        Server Error
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```

你可以调用插槽的 `isEmpty` 方法来判断插槽是否包含内容：

```blade
<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    @if ($slot->isEmpty())
        如果插槽为空，则显示此默认内容。
    @else
        {{ $slot }}
    @endif
</div>
```



此外，`hasActualContent` 方法可以用于判断插槽是否包含任何不是 HTML 注释的“实际”内容：

```blade
@if ($slot->hasActualContent())
    作用域包含非注释内容。
@endif
```

#### 作用域插槽（Scoped Slots）

如果你使用过 Vue 等 JavaScript 框架，你可能熟悉“作用域插槽”（scoped slots），它允许你在插槽中访问组件的数据或方法。在 Laravel 中，你可以通过在组件上定义公共方法或属性，并通过 `$component` 变量在插槽中访问组件，从而实现类似的行为。在此示例中，我们假设 `x-alert` 组件的组件类中定义了一个公共的 `formatAlert` 方法：

```blade
<x-alert>
    <x-slot:title>
        {{ $component->formatAlert('Server Error') }}
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```

#### 插槽属性

与 Blade 组件一样，你可以为插槽分配额外的[属性](#component-attributes)，例如 CSS 类名：

```xml
<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` 属性。有关如何操作属性的更多信息，请参考[组件属性](#component-attributes)文档：

```blade
@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
/**
 * 获取表示组件的视图 / 内容。
 */
public function render(): string
{
    return <<<'blade'
        <div class="alert alert-danger">
            {{ $slot }}
        </div>
    blade;
}
```



#### 生成内联视图组件

要创建一个渲染内联视图的组件，可以在执行 `make:component` 命令时使用 `inline` 选项：

```shell
php artisan make:component Alert --inline
```

### 动态组件

有时你可能需要渲染一个组件，但直到运行时才知道应该渲染哪个组件。在这种情况下，你可以使用 Laravel 内置的 `dynamic-component` 组件，根据运行时的值或变量来渲染组件：

```blade
// $componentName = "secondary-button";

<x-dynamic-component :component="$componentName" class="mt-4" />
```

### 手动注册组件

> [!警告]
> 以下关于手动注册组件的文档主要适用于编写包含视图组件的 Laravel 扩展包的开发者。如果你不是在编写扩展包，这部分组件文档可能与你无关。

在为自己的应用程序编写组件时，组件会自动从 `app/View/Components` 目录和 `resources/views/components` 目录中被发现。

但是，如果你正在构建一个使用 Blade 组件的扩展包，或者将组件放置在非约定目录中，则需要手动注册组件类及其 HTML 标签别名，以便 Laravel 知道在哪里查找该组件。通常，你应该在扩展包服务提供者的 `boot` 方法中注册组件：

```php
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

/**
 * 启动扩展包服务。
 */
public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}
```

组件注册完成后，可以使用其标签别名进行渲染：

```blade
<x-package-alert/>
```



#### 自动加载扩展包组件

另外，你可以使用 `componentNamespace` 方法按照约定自动加载组件类。例如，一个 `Nightshade` 扩展包可能具有位于 `Package\Views\Components` 命名空间中的 `Calendar` 和 `ColorPicker` 组件：

```php
use Illuminate\Support\Facades\Blade;

/**
 * 启动扩展包服务。
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
```

这将允许通过供应商命名空间使用扩展包组件，使用 `package-name::` 语法：

```blade
<x-nightshade::calendar />
<x-nightshade::color-picker />
```

Blade 会通过将组件名称转换为 Pascal 命名格式，自动检测与该组件关联的类。也支持使用“点”号表示法访问子目录。

## 匿名组件

与内联组件类似，匿名组件提供了一种通过单个文件管理组件的机制。但是，匿名组件使用单个视图文件，并且没有关联的类。要定义一个匿名组件，你只需要在 `resources/views/components` 目录中放置一个 Blade 模板。例如，假设你已经在 `resources/views/components/alert.blade.php` 定义了一个组件，你可以像这样简单地渲染它：

```blade
<x-alert/>
```

你可以使用 `.` 字符来表示组件是否嵌套在 `components` 目录更深层的位置。例如，假设组件定义在 `resources/views/components/inputs/button.blade.php`，你可以像这样渲染它：

```blade
<x-inputs.button/>
```

要通过 Artisan 创建匿名组件，可以在调用 `make:component` 命令时使用 `--view` 标志：

```shell
php artisan make:component forms.input --view
```

上述命令将在 `resources/views/components/forms/input.blade.php` 创建一个 Blade 文件，该文件可以通过 `<x-forms.input />` 作为组件进行渲染。



### 匿名索引组件

有时，当一个组件由多个 Blade 模板组成时，你可能希望将给定组件的模板组织在一个单独的目录中。例如，假设有一个“accordion”组件，其目录结构如下：

```text
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php
```

此目录结构允许你像这样渲染 accordion 组件及其 item：

```blade
<x-accordion>
    <x-accordion.item>
        ...
    </x-accordion.item>
</x-accordion>
```

但是，为了通过 `x-accordion` 渲染 accordion 组件，我们被迫将“索引” accordion 组件模板放置在 `resources/views/components` 目录中，而不是与其他 accordion 相关模板一起嵌套在 `accordion` 目录中。

幸运的是，Blade 允许你在组件目录本身中放置一个与组件目录名称匹配的文件。当这个模板存在时，即使它嵌套在目录中，也可以作为组件的“根”元素进行渲染。因此，我们可以继续使用上面示例中的相同 Blade 语法；但是，我们会调整目录结构如下：

```text
/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php
```

### 数据属性 / 属性

由于匿名组件没有关联的类，你可能会想知道如何区分哪些数据应该作为变量传递给组件，以及哪些属性应该放置到组件的[属性包](#component-attributes)中。

你可以在组件 Blade 模板顶部使用 `@props` 指令指定哪些属性应该被视为数据变量。组件上的所有其他属性都可以通过组件的属性包获取。如果你希望为某个数据变量指定默认值，可以将变量名称指定为数组键，并将默认值作为数组值：

```blade
<!-- /resources/views/components/alert.blade.php -->

@props(['type' => 'info', 'message'])

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>
```



根据上面的组件定义，我们可以像这样渲染组件：

```blade
<x-alert type="error" :message="$message" class="mb-4"/>
```

### 访问父级数据

有时你可能希望在子组件内部访问父组件中的数据。在这种情况下，你可以使用 `@aware` 指令。例如，假设我们正在构建一个复杂的菜单组件，它由一个父级 `<x-menu>` 和子级 `<x-menu.item>` 组成：

```blade
<x-menu color="purple">
    <x-menu.item>...</x-menu.item>
    <x-menu.item>...</x-menu.item>
</x-menu>
```

`<x-menu>` 组件可能具有如下实现：

```blade
<!-- /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>` 内部可用：

```blade
<!-- /resources/views/components/menu/item.blade.php -->

@aware(['color' => 'gray'])

<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
    {{ $slot }}
</li>
```

> [!警告]
> `@aware` 指令无法访问未通过 HTML 属性显式传递给父组件的父级数据。未显式传递给父组件的默认 `@props` 值无法通过 `@aware` 指令访问。

### 匿名组件路径

如前所述，匿名组件通常通过将 Blade 模板放置在 `resources/views/components` 目录中来定义。但是，有时除了默认路径之外，你可能还希望向 Laravel 注册其他匿名组件路径。



`anonymousComponentPath` 方法的第一个参数接收匿名组件位置的“路径”，第二个参数接收可选的“命名空间”，用于指定组件应该被放置在哪个命名空间下。通常情况下，该方法应该从应用程序某个[服务提供者](/docs/laravel/13.x/providers)的 `boot` 方法中调用：

```php
/**
 * 启动应用程序服务。
 */
public function boot(): void
{
    Blade::anonymousComponentPath(__DIR__.'/../components');
}
```

当组件路径注册时没有像上面示例中那样指定前缀时，它们也可以在 Blade 组件中不带对应前缀进行渲染。例如，如果在上述注册路径中存在一个 `panel.blade.php` 组件，则可以像这样渲染：

```blade
<x-panel />
```

可以将前缀“命名空间”作为 `anonymousComponentPath` 方法的第二个参数提供：

```php
Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
```

当提供了前缀后，该“命名空间”中的组件可以在渲染时通过将组件命名空间作为前缀添加到组件名称前来进行渲染：

```blade
<x-dashboard::panel />
```

## 构建布局

### 使用组件构建布局

大多数 Web 应用程序会在不同页面之间保持相同的通用布局。如果我们必须在创建的每个视图中重复整个布局 HTML，那么维护应用程序将会非常繁琐且困难。幸运的是，将这个布局定义为一个单独的 [Blade 组件](#components)，然后在整个应用程序中使用它，是非常方便的。

#### 定义布局组件

例如，假设我们正在构建一个“todo”列表应用程序。我们可能会定义一个如下所示的 `layout` 组件：

```blade
<!-- resources/views/components/layout.blade.php -->

<html>
    <head>
        <title>{{ $title ?? 'Todo Manager' }}</title>
    </head>
    <body>
        <h1>Todos</h1>
        <hr/>
        {{ $slot }}
    </body>
</html>
```



#### 应用布局组件

一旦定义了 `layout` 组件，我们就可以创建一个使用该组件的 Blade 视图。在这个示例中，我们将定义一个简单的视图，用于显示任务列表：

```blade
<!-- resources/views/tasks.blade.php -->

<x-layout>
    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>
```

请记住，注入到组件中的内容会被提供给 `layout` 组件中的默认 `$slot` 变量。正如你可能已经注意到的，我们的 `layout` 也支持 `$title` 插槽（如果提供的话）；否则，将显示默认标题。我们可以使用组件文档中讨论的标准插槽语法，从任务列表视图中注入自定义标题：

```blade
<!-- resources/views/tasks.blade.php -->

<x-layout>
    <x-slot:title>
        自定义标题
    </x-slot>

    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>
```

现在我们已经定义好了布局视图和任务列表视图，我们只需要从路由中返回 `task` 视图：

```php
use App\Models\Task;

Route::get('/tasks', function () {
    return view('tasks', ['tasks' => Task::all()]);
});
```

### 使用模板继承的布局

#### 定义布局

布局也可以通过“模板继承”方式创建。在引入[组件](#components)之前，这是构建应用程序的主要方式。

开始之前，让我们看一个简单的示例。首先，我们将查看一个页面布局。由于大多数 Web 应用程序在不同页面之间都会保持相同的整体布局，因此将这个布局定义为一个单独的 Blade 视图会更加方便：

```blade
<!-- resources/views/layouts/app.blade.php -->

<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            这是主侧边栏。
        @show

        <div class="container">
            @yield('content')
        </div>
    </body>
</html>
```



正如你所看到的，这个文件包含了典型的 HTML 标记。不过，请注意 `@section` 和 `@yield` 指令。`@section` 指令顾名思义，用于定义一段内容区域，而 `@yield` 指令则用于显示指定区域的内容。

现在我们已经为应用程序定义好了一个布局，接下来让我们定义一个继承该布局的子页面。

#### 扩展布局

在定义子视图时，请使用 `@extends` Blade 指令来指定子视图应该“继承”的布局。扩展 Blade 布局的视图可以通过 `@section` 指令向布局的各个区域注入内容。请记住，正如上面的示例所示，这些区域中的内容会通过布局中的 `@yield` 显示：

```blade
<!-- 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` 指令还可以接受第二个参数作为默认值。如果被输出的区域未定义，则会渲染该默认值：

```blade
@yield('content', 'Default content')
```



## 表单

### CSRF 字段

每当你在应用程序中定义 HTML 表单时，都应该在表单中包含一个隐藏的 CSRF 令牌字段，以便 [CSRF 保护](/docs/laravel/13.x/csrf) 中间件可以验证请求。你可以使用 `@csrf` Blade 指令来生成令牌字段：

```blade
<form method="POST" action="/profile">
    @csrf

    ...
</form>
```

### Method 字段

由于 HTML 表单无法发起 `PUT`、`PATCH` 或 `DELETE` 请求，因此你需要添加一个隐藏的 `_method` 字段来伪造这些 HTTP 请求方法。`@method` Blade 指令可以为你创建该字段：

```blade
<form action="/foo/bar" method="POST">
    @method('PUT')

    ...
</form>
```

### 验证错误

`@error` 指令可以用于快速检查某个指定属性是否存在[验证错误消息](/docs/laravel/13.x/validation#quick-displaying-the-validation-errors)。在 `@error` 指令内部，你可以输出 `$message` 变量来显示错误消息：

```blade
<!-- /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` 指令，在某个属性不存在错误时渲染对应内容：

```blade
<!-- /resources/views/auth.blade.php -->

<label for="email">邮箱地址</label>

<input
    id="email"
    type="email"
    class="@error('email') is-invalid @else is-valid @enderror"
/>
```

你可以将[指定错误包的名称](/docs/laravel/13.x/validation#named-error-bags)作为第二个参数传递给 `@error` 指令，以便在包含多个表单的页面中获取对应表单的验证错误消息：

```blade
<!-- /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 库特别有用：

```blade
@push('scripts')
    <script src="/example.js"></script>
@endpush
```

如果你希望仅在给定的布尔表达式计算结果为 `true` 时才 `@push` 内容，可以使用 `@pushIf` 指令：

```blade
@pushIf($shouldPush, 'scripts')
    <script src="/example.js"></script>
@endPushIf
```

你可以根据需要多次向栈中推送内容。要渲染完整的栈内容，请将栈的名称传递给 `@stack` 指令：

```blade
<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>
```

如果你希望将内容添加到栈的开头，则应该使用 `@prepend` 指令：

```blade
@push('scripts')
    This will be second...
@endpush

// Later...

@prepend('scripts')
    This will be first...
@endprepend
```

`@hasstack` 指令可用于判断某个栈是否为空：

```blade
@hasstack('list')
    <ul>
        @stack('list')
    </ul>
@endif
```

## 服务注入

`@inject` 指令可用于从 Laravel [服务容器](/docs/laravel/13.x/container)中获取服务。传递给 `@inject` 的第一个参数是服务存放的变量名称，而第二个参数则是你希望解析的服务类或接口名称：

```blade
@inject('metrics', 'App\Services\MetricsService')

<div>
    Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>
```

## 渲染内联 Blade 模板

有时你可能需要将原始 Blade 模板字符串转换为有效的 HTML。你可以使用 `Blade` 门面的 `render` 方法来完成此操作。`render` 方法接收 Blade 模板字符串，以及一个可选的数据数组，用于提供给模板：

```php
use Illuminate\Support\Facades\Blade;

return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
```



Laravel 通过将内联 Blade 模板写入 `storage/framework/views` 目录来渲染它们。如果你希望 Laravel 在渲染 Blade 模板后删除这些临时文件，可以向该方法提供 `deleteCachedView` 参数：

```php
return Blade::render(
    'Hello, {{ $name }}',
    ['name' => 'Julian Bashir'],
    deleteCachedView: true
);
```

## 渲染 Blade 片段

当使用诸如 [Turbo](https://turbo.hotwired.dev/) 和 [htmx](https://htmx.org/) 等前端框架时，你可能偶尔只需要在 HTTP 响应中返回 Blade 模板的一部分内容。Blade “片段（fragments）”功能正是用于实现这一点的。

开始使用时，将 Blade 模板的一部分内容放置在 `@fragment` 和 `@endfragment` 指令之间：

```blade
@fragment('user-list')
    <ul>
        @foreach ($users as $user)
            <li>{{ $user->name }}</li>
        @endforeach
    </ul>
@endfragment
```

然后，在渲染使用该模板的视图时，你可以调用 `fragment` 方法，以指定只有指定的片段应包含在发送出去的 HTTP 响应中：

```php
return view('dashboard', ['users' => $users])->fragment('user-list');
```

`fragmentIf` 方法允许你根据给定条件有条件地返回视图中的某个片段。否则，将返回整个视图：

```php
return view('dashboard', ['users' => $users])
    ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
```

`fragments` 和 `fragmentsIf` 方法允许你在响应中返回多个视图片段。这些片段会被连接在一起：

```php
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`，其中 `$var` 应该是 `DateTime` 的实例：

```php
<?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
<?php echo ($var)->format('m/d/Y H:i'); ?>
```

> [!警告]
> 在更新 Blade 指令的逻辑之后，你需要删除所有缓存的 Blade 视图。可以使用 `view:clear` Artisan 命令删除缓存的 Blade 视图。

### 自定义 Echo 处理器

如果你尝试使用 Blade "输出" 一个对象，对象的 `__toString` 方法将会被调用。[__toString](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring) 方法是 PHP 内置的 "魔术方法" 之一。但是，有时候你可能无法控制某个类的 `__toString` 方法，例如当你正在使用的类属于第三方库时。



在这些情况下，Blade 允许你为特定类型的对象注册自定义的 echo 处理器。要实现这一点，你应该调用 Blade 的 `stringable` 方法。`stringable` 方法接受一个闭包。该闭包应该通过类型提示指定它负责渲染的对象类型。通常，`stringable` 方法应该在应用程序的 `AppServiceProvider` 类的 `boot` 方法中调用：

```php
use Illuminate\Support\Facades\Blade;
use Money\Money;

/**
 * 启动任何应用服务。
 */
public function boot(): void
{
    Blade::stringable(function (Money $money) {
        return $money->formatTo('en_GB');
    });
}
```

一旦定义了自定义 echo 处理器，你就可以直接在 Blade 模板中输出该对象：

```blade
Cost: {{ $money }}
```

### 自定义 If 语句

在定义简单的自定义条件语句时，编写一个自定义指令有时会比实际需要的更加复杂。因此，Blade 提供了一个 `Blade::if` 方法，它允许你使用闭包快速定义自定义条件指令。例如，让我们定义一个自定义条件，用于检查应用程序配置的默认 "disk"。我们可以在 `AppServiceProvider` 的 `boot` 方法中完成此操作：

```php
use Illuminate\Support\Facades\Blade;

/**
 * 启动任何应用服务。
 */
public function boot(): void
{
    Blade::if('disk', function (string $value) {
        return config('filesystems.default') === $value;
    });
}
```

定义了自定义条件之后，你可以在模板中使用它：

```blade
@disk('local')
    <!-- 应用程序正在使用本地磁盘... -->
@elsedisk('s3')
    <!-- 应用程序正在使用 s3 磁盘... -->
@else
    <!-- 应用程序正在使用其他磁盘... -->
@enddisk

@unlessdisk('local')
    <!-- 应用程序未使用本地磁盘... -->
@enddisk
```

