laravel
4. 基础知识
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 (opens in a new tab)。Livewire 允许您编写 Blade 组件,这些组件具有动态功能,通常只有通过像 React 或 Vue 这样的前端框架才能实现,为构建现代、响应式前端提供了一种很好的方法,而无需许多 JavaScript 框架的复杂性、客户端渲染或构建步骤。

显示数据

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

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

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

Hello, {{ $name }}.

[!注意]
Blade 的{{ }}回显语句会自动通过 PHP 的htmlspecialchars函数进行处理,以防止 XSS 攻击。

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

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

HTML 实体编码

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

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动任何应用程序服务。
     */
    public function boot(): void
    {
        Blade::withoutDoubleEncoding();
    }
}

显示未转义的数据

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

Hello, {!! $name!!}.

[!警告]
在回显应用程序用户提供的内容时要非常小心。在显示用户提供的数据时,通常应该使用转义的、双花括号语法来防止 XSS 攻击。

Blade 和 JavaScript 框架

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

<h1>Laravel</h1>
 
Hello, @{{ name }}.

在这个例子中,@符号将被 Blade 移除;然而,{{ name }}表达式将不会被 Blade 引擎处理,允许它由您的 JavaScript 框架进行渲染。

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

{{-- Blade 模板 --}}
@@if()
 
<!-- HTML 输出 -->
@if()

渲染 JSON

有时,您可能会将一个数组传递到您的视图中,目的是将其作为 JSON 进行渲染,以便初始化一个 JavaScript 变量。例如:

<script>
    var app = <?php echo json_encode($array);?>;
</script>

但是,您可以使用Illuminate\Support\Js::from方法指令来代替手动调用json_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 变量,您可以将 HTML 包装在@verbatim指令中,这样您就不必在每个 Blade 回显语句前加上@符号:

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

Blade 指令

除了模板继承和显示数据外,Blade 还为常见的 PHP 控制结构(如条件语句和循环)提供了方便的快捷方式。这些快捷方式为使用 PHP 控制结构提供了一种非常干净、简洁的方式,同时也与它们的 PHP 对应物保持熟悉。

If 语句

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

@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif

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

@unless (Auth::check())
    You are not signed in.
@endunless

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

@isset($records)
    // $records 已定义且不为空...
@endisset
 
@empty($records)
    // $records 为空...
@endempty

认证指令

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

@auth
    // 用户已认证...
@endauth
 
@guest
    // 用户未认证...
@endguest

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

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

环境指令

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

@production
    // 生产环境特定的内容...
@endproduction

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

@env('staging')
    // 应用程序正在"staging"环境中运行...
@endenv
 
@env(['staging', 'production'])
    // 应用程序正在"staging"或"production"环境中运行...
@endenv

部分指令

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

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

您可以使用sectionMissing指令确定一个部分是否没有内容:

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

会话指令

@session指令可用于确定会话值是否存在。如果会话值存在,@session@endsession指令内的模板内容将被评估。在@session指令的内容中,您可以回显$value变量来显示会话值:

@session('status')
    <div class="p-4 bg-green-100">
        {{ $value }}
    </div>
@endsession

Switch 语句

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

@switch($i)
    @case(1)
        First case...
        @break
 
    @case(2)
        Second case...
        @break
 
    @default
        Default case...
@endswitch

循环

除了条件语句外,Blade 还为使用 PHP 的循环结构提供了简单的指令。同样,这些指令的每个功能都与其 PHP 对应物完全相同:

@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor
 
@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach
 
@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse
 
@while (true)
    <p>I'm looping forever.</p>
@endwhile

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

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

@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif
 
    <li>{{ $user->name }}</li>
 
    @if ($user->number == 5)
        @break
    @endif
@endforeach

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

@foreach ($users as $user)
    @continue($user->type == 1)
 
    <li>{{ $user->name }}</li>
 
    @break($user->number == 5)
@endforeach

循环变量

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

@foreach ($users as $user)
    @if ($loop->first)
        This is the first iteration.
    @endif
 
    @if ($loop->last)
        This is the last iteration.
    @endif
 
    <p>This is user {{ $user->id }}</p>
@endforeach

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

@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            This is the first iteration of the parent loop.
        @endif
    @endforeach
@endforeach

$loop变量还包含许多其他有用的属性:

属性描述
$loop->index当前循环迭代的索引(从 0 开始)。
$loop->iteration当前循环迭代(从 1 开始)。
$loop->remaining循环中剩余的迭代次数。
$loop->count正在迭代的数组中的项目总数。
$loop->first是否是循环的第一次迭代。
$loop->last是否是循环的最后一次迭代。
$loop->even是否是循环的偶数次迭代。
$loop->odd是否是循环的奇数次迭代。
$loop->depth当前循环的嵌套级别。
$loop->parent在嵌套循环中,父级的循环变量。

条件类和样式

@class指令有条件地编译一个 CSS 类字符串。该指令接受一个类数组,其中数组键包含您希望添加的类或类,而值是一个布尔表达式。如果数组元素具有数字键,它将始终包含在渲染的类列表中:

@php
    $isActive = false;
    $hasError = true;
@endphp
 
<span @class([
    'p-4',
    'font-bold' => $isActive,
    'text-gray-500' =>! $isActive,
    'bg-red' => $hasError,
])></span>
 
<span class="p-4 text-gray-500 bg-red"></span>

同样,@style指令可用于有条件地向 HTML 元素添加内联 CSS 样式:

@php
    $isActive = true;
@endphp
 
<span @style([
    'background-color: red',
    'font-weight: bold' => $isActive,
])></span>
 
<span style="background-color: red; font-weight: bold;"></span>

附加属性

为了方便起见,您可以使用@checked指令轻松指示给定的 HTML 复选框输入是否“被选中”。如果提供的条件计算结果为true,则该指令将回显checked

<input type="checkbox"
        name="active"
        value="active"
        @checked(old('active', $user->active)) />

同样,@selected指令可用于指示给定的选择选项是否应“被选中”:

<select name="version">
    @foreach ($product->versions as $version)
        <option value="{{ $version }}" @selected(old('version') == $version)>
            {{ $version }}
        </option>
    @endforeach
</select>

此外,@disabled指令可用于指示给定元素是否应“被禁用”:

<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>

组件

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

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

php artisan make:component Alert

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

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

php artisan make:component Forms/Input

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

如果您想创建一个匿名组件(只有 Blade 模板而没有类的组件),可以在调用 make:component 命令时使用 --view 标志:

php artisan make:component forms.input --view

上面的命令将在 resources/views/components/forms/input.blade.php 创建一个 Blade 文件,它可以通过 <x-forms.input /> 作为组件进行渲染。

手动注册包组件

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

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

use Illuminate\Support\Facades\Blade;

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

一旦您的组件已注册,就可以使用其标签别名进行渲染:

<x-package-alert/>

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

use Illuminate\Support\Facades\Blade;

/**
 * 引导您的包的服务。
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}

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

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

Blade 将通过将组件名称转换为帕斯卡命名法自动检测与此组件关联的类。子目录也可以使用“点”符号支持。

渲染组件

要显示一个组件,您可以在您的一个 Blade 模板中使用 Blade 组件标签。Blade 组件标签以字符串 x- 开头,后面跟着组件类的烤肉串式名称:

<x-alert/>
 
<x-user-profile/>

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

<x-inputs.button/>

如果您想有条件地渲染您的组件,您可以在您的组件类上定义一个 shouldRender 方法。如果 shouldRender 方法返回 false,则组件不会被渲染:

use Illuminate\Support\Str;

/**
 * 组件是否应该被渲染
 */
public function shouldRender(): bool
{
    return Str::length($this->message) > 0;
}

向组件传递数据

您可以使用 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
{
    /**
     * 创建组件实例。
     */
    public function __construct(
        public string $type,
        public string $message,
    ) {}

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

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

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

大小写

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

/**
 * 创建组件实例。
 */
public function __construct(
    public string $alertType,
) {}

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

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

短属性语法

向组件传递属性时,您也可以使用“短属性”语法。这通常很方便,因为属性名称经常与它们对应的变量名称相匹配:

{{-- 短属性语法... --}}
<x-profile :$userId :$name />
 
{{-- 等同于... --}}
<x-profile :user-id="$userId" :name="$name" />

转义属性渲染

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

<x-button ::class="{ danger: isDeleting }">
    Submit
</x-button>

Blade 将渲染以下 HTML:

<button :class="{ danger: isDeleting }">
    Submit
</button>

组件方法

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

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

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

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

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

Blade 组件还允许您在类的 render 方法中访问组件名称、属性和插槽。但是,为了访问此数据,您应该从组件的 render 方法返回一个闭包:

use Closure;

/**
 * 获取表示组件的视图/内容。
 */
public function render(): Closure
{
    return function () {
        return '<div {{ $attributes }}>组件内容</div>';
    };
}

组件的 render 方法返回的闭包也可以接收一个 $data 数组作为其唯一参数。此数组将包含几个元素,这些元素提供有关组件的信息:

return function (array $data) {
    // $data['componentName'];
    // $data['attributes'];
    // $data['slot'];

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

[!WARNING] $data 数组中的元素不应直接嵌入到您的 render 方法返回的 Blade 字符串中,因为这样做可能会通过恶意属性内容允许远程代码执行。

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

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

附加依赖项

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

use App\Services\AlertCreator;
 
/**
 * 创建组件实例。
 */
public function __construct(
    public AlertCreator $creator,
    public string $type,
    public string $message,
) {}

隐藏属性/方法

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

<?php

namespace App\View\Components;

use Illuminate\View\Component;

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

    /**
     * 创建组件实例。
     */
    public function __construct(
        public string $type,
    ) {}
}

组件属性

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

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

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

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

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

默认/合并属性

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

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

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

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

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

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

有条件地合并类

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

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

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

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

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

非类属性合并

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

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

要使用自定义 type 渲染按钮组件,可以在使用组件时指定。如果未指定 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>类属性存在</div>
@endif

如果将数组传递给 has 方法,该方法将确定给定的所有属性是否都存在于组件上:

@if ($attributes->has(['name', 'class']))
    <div>所有属性都存在</div>
@endif

hasAny 方法可用于确定给定的属性中是否有任何一个存在于组件上:

@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
    <div>其中一个属性存在</div>
@endif

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

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

保留关键字

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

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

插槽

您经常需要通过“插槽”将其他内容传递给您的组件。组件插槽通过回显 $slot 变量进行渲染。为了探讨这个概念,让我们假设一个 alert 组件具有以下标记:

<!-- /resources/views/components/alert.blade.php -->
 
<div class="alert alert-danger">
    {{ $slot }}
</div>

我们可以通过将内容注入到组件中来向 slot 传递内容:

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

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

<!-- /resources/views/components/alert.blade.php -->
 
<span class="alert-title
#### 自动加载包组件
 
或者,您可以使用 `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:: 语法通过其供应商命名空间使用包组件:

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

Blade 将通过将组件名称转换为帕斯卡命名法来自动检测与此组件关联的类。也支持使用“点”符号表示子目录。

匿名组件

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

<x-alert/>

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

<x-inputs.button/>

匿名索引组件

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

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

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

<x-accordion>
    <x-accordion.item>
       ...
    </x-accordion.item>
</x-accordion>

但是,为了通过 x-accordion 渲染accordion组件,我们不得不将“索引”accordion组件模板放在 resources/views/components 目录中,而不是将其与其他与accordion相关的模板一起嵌套在 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 属性仅传递到父级(<x-menu>),所以它在 <x-menu.item> 中不可用。但是,如果我们使用 @aware 指令,我们也可以在 <x-menu.item> 中使其可用:

<!-- /resources/views/components/menu/item.blade.php -->
 
@aware(['color' => 'gray'])
 
<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
    {{ $slot }}
</li>

[!WARNING]
@aware 指令无法访问未通过 HTML 属性明确传递给父组件的父级数据。未明确传递给父组件的默认 @props 值无法被 @aware 指令访问。

匿名组件路径

如前所述,匿名组件通常通过在 resources/views/components 目录中放置一个 Blade 模板来定义。但是,除了默认路径之外,您可能偶尔希望向 Laravel 注册其他匿名组件路径。

anonymousComponentPath 方法将匿名组件位置的“路径”作为其第一个参数,并将组件应放置在其下的可选“命名空间”作为其第二个参数。通常,此方法应从您的应用程序的服务提供者之一的 boot 方法中调用:

/**
 * 启动任何应用程序服务。
 */
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)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>

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

<!-- resources/views/tasks.blade.php -->
 
<x-layout>
    <x-slot:title>
        Custom Title
    </x-slot>
 
    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>

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

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

使用模板继承的布局

定义布局

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

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

<!-- resources/views/layouts/app.blade.php -->
 
<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            This is the master sidebar.
        @show
 
        <div class="container">
            @yield('content')
        </div>
    </body>
</html>

如您所见,此文件包含典型的 HTML 标记。但是,请注意 @section@yield 指令。顾名思义,@section 指令定义了内容的一个部分,而 @yield 指令用于显示给定部分的内容。

现在我们已经为我们的应用程序定义了一个布局,让我们定义一个继承该布局的子页面。

扩展布局

在定义子视图时,使用 @extends Blade 指令指定子视图应“继承”的布局。扩展 Blade 布局的视图可以使用 @section 指令将内容注入到布局的部分中。请记住,如上面的示例所示,这些部分的内容将在布局中使用 @yield 显示:

<!-- resources/views/child.blade.php -->
 
@extends('layouts.app')
 
@section('title', 'Page Title')
 
@section('sidebar')
    @@parent
 
    <p>This is appended to the master sidebar.</p>
@endsection
 
@section('content')
    <p>This is my body content.</p>
@endsection

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

[!NOTE]
与前面的示例不同,这个 sidebar 部分以 @endsection 而不是 @show 结束。@endsection 指令只会定义一个部分,而 @show 会定义并立即生成该部分。

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

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

表单

CSRF 字段

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

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

方法字段

由于 HTML 表单无法发出 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 通过将内联 Blade 模板写入 storage/framework/views 目录来渲染它们。如果您希望 Laravel 在渲染 Blade 模板后删除这些临时文件,可以将 deleteCachedView 参数提供给该方法:

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

渲染 Blade 片段

当使用诸如 Turbo (opens in a new tab)htmx (opens in a new tab) 之类的前端框架时,您可能偶尔需要在 HTTP 响应中仅返回 Blade 模板的一部分。Blade“片段”允许您做到这一点。首先,将您的 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 方法允许您根据给定条件有条件地返回视图的片段。否则,将返回整个视图: