Blade 模板

未匹配的标注

Blade 模板

简介

Blade 是 Laravel 提供的一个简单而又强大的模板引擎。 和其他流行的 PHP 模板引擎不同,Blade 并不限制你在视图中使用原生 PHP 代码。实际上, 所有 Blade 视图文件都将被编译成原生的 PHP 代码并缓存起来,除非它被修改,否则不会重新编译,这就意味着 Blade 基本上不会给你的应用增加任何负担。Blade 模板文件使用 .blade.php 作为文件扩展名,被存放在 resources/views 目录。

Blade 视图可以使用全局 view 函数从 Route 或控制器返回。当然,正如有关 views 的文档中所描述的,可以使用 view 函数的第二个参数将数据传递到 Blade 视图:

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

技巧:在深入研究 Blade 之前,请务必阅读 Laravel view 文档

显示数据

你可以把变量置于花括号中以在视图中显示数据。 例如,给定下方的路由:

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

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

Hello, {{ $name }}.

技巧:Blade 的 {{ }} 语句将被 PHP 的 htmlspecialchars 函数自动转义以防范 XSS 攻击。

您不仅限于显示传递给视图的变量的内容。 您也可以回显任何PHP函数的结果。 实际上,您可以将所需的任何PHP代码放入Blade echo语句中:

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

渲染 JSON

有时,您可能会将数组传递给视图,以将其呈现为JSON,以便初始化JavaScript变量。 例如:

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

当然,您亦可使用 @json Blade 指令来代替手动调用 json_encode 方法。 @json 指令的参数和 PHP 的 json_encode 函数一致:

<script>
    var app = @json($array);

    var app = @json($array, JSON_PRETTY_PRINT);
</script>

注意:使用 @json 指令时,您应该只渲染已经存在的变量为 JSON 。 Blade 模板是基于正则表达式的,如果尝试将一个复杂表达式传递给 @json 指令可能会导致无法预测的错误。

HTML 实体编码

默认情况下, Blade (以及 Laravel 的帮助函数 e)会调用 PHP 的 htmlentities 函数来对 HTML 实体进行双重编码。如果不想这样,请在 AppServiceProvider 文件 boot 方法下调用 Blade::withoutDoubleEncoding 方法:

<?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 攻击。如果不想您的数据被转义,那么您可使用如下的语法:

Hello, {!! $name !!}.

注意:在应用中显示用户提供的数据时请格外小心,请尽可能的使用转义和双引号语法来防范 XSS 攻击。

Blade & JavaScript 框架

由于许多 JavaScript 框架也使用「花括号」来标识将显示在浏览器中的表达式,因此,您可以使用 @ 符号来表示 Blade 渲染引擎应当保持不变。例如:

<h1>Laravel</h1>

Hello, @{{ name }}.

在这个例子中, @ 符号将被 Blade 移除;当然,Blade 将不会修改 {{ name }} 表达式,取而代之的是 JavaScript 模板来对其进行渲染。

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

{{-- Blade 模板 --}}
@@json()

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

@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)
    // 有一条记录
@elseif (count($records) > 1)
    // 多条记录
@else
    // 没有记录
@endif

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

@unless (Auth::check())
    // 还没有登录
@endunless

译注:相当于 @if (! Auth::check()) @endif

除了上面所说条件指令外, @isset 和 @empty 指令亦可作为它们所对应的 PHP 函数的快捷方式:

@isset($records)
    // $records 已经被定义且不为 null ……
@endisset

@empty($records)
    // $records 为「空」……
@endempty

授权指令

@auth 和 @guest 指令可用于快速判断当前用户是否已经获得 授权 或是游客:

@auth
    // 用户已经通过认证……
@endauth

@guest
    // 用户没有通过认证……
@endguest

如有需要,您亦可在使用 @auth 和 @guest 指令时指定鉴权守卫:

@auth('admin')
    // 用户已经通过认证……
@endauth

@guest('admin')
    // 用户没有通过认证……
@endguest

环境指令

您可以使用 @production 指令来判断当前应用是否处于生产环境:

@production
    // 生产环境执行的逻辑语句……
@endproduction

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

@env('staging')
    // 该应用运行在 「staging」 环境中...
@endenv

@env(['staging', 'production'])
    // 该应用运行在 「staging」和 「production」 环境中...
@endenv

hasSection 指令

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

Switch 语句

switch 语句可以使用 @switch, @case, @break, @default@endswitch 指令进行构造:

@switch($i)
    @case(1)
        第一个用例 ...
        @break

    @case(2)
        第二个用例 ...
        @break

    @default
        其他用例 ...
@endswitch

循环

除了条件语句之外,Blade 还提供了使用 PHP 循环结构的简单指令。同样,这些指令中的每一个的功能与它们的 PHP 对应的指令相同:

@for ($i = 0; $i < 10; $i++)
    当前的值是 {{ $i }}
@endfor

@foreach ($users as $user)
    <p>这是 {{ $user->id }} 用户</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>没有用户</p>
@endforelse

@while (true)
    <p>我正在循环,直到天荒地老</p>
@endwhile

技巧:循环时,你可以使用 loop variable 获取有关循环的有价值的信息,例如你是在循环的第一次还是最后一次迭代中。

使用循环时,还可以使用 @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

循环变量

循环时,可以在循环内使用 $loop 变量将在你的循环中可用。这个变量提供了一些有用的信息,比如当前循环的索引,以及这是循环的第一次还是最后一次迭代:

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

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

    <p>This is user {{ $user->id }}</p>
@endforeach

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

@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 在嵌套循环中,父循环变量。

注释

Blade 模板允许您在视图中添加注释. 但是, 不同于 HTML 的注释, Blade 模板的注释不会包含在应用程序返回的 HTML 中:

{{-- 这个注释将不会显示在 HTML 中 --}}

包含子视图

技巧:虽然您可以随意的使用 @include 指令, 但是 Blade components 提供了类似的功能, 并且比 @include 多了一些功能, 如数据和属性绑定.

Blade 的 @include 指令允许您从一个视图中包含另外一个 Blade 视图. 父视图中的所有变量在子视图中都可以使用:

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

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

尽管子视图可以继承父视图中所有可以使用的数据, 但是您也可以传递一个额外的数组, 这个数组在子视图中也可以使用:

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

如果您想要使用 @include 包含一个不存在的视图, Laravel 将会抛出一个错误. 如果您想要包含一个可能存在也可能不存在的视图, 那么您应该使用 @includeIf 指令:

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

如果想要使用 @include 包含一个给定值为 truefalse 的布尔表达式的视图, 那么您可以使用 @includeWhen 或者 @includeUnless 指令:

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

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

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

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

注意:在视图中,您应该避免使用 __DIR____FILE__ 这些常量, 因为他们将引用已缓存的和已编译的视图.

为集合渲染视图

您可以使用 Blade 的 @each 指令将循环合并在一行内:

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

@each 指令的第一个参数是数组或集合中的元素的要渲染的视图片段。第二个参数是您想要迭代的数组或集合,当第三个参数是一个表示当前迭代的视图的变量名。因此,如果您遍历一个名为 jobs 的数组,通常会在视图片段中使用 job 变量来访问每一个 job (jobs 数组的元素)。在您的视图片段中,可以使用 key 变量来访问当前迭代的键。

您也可以传递第四个参数给 @each 指令。当给定的数组为空时,将会渲染该参数所对应的视图。

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

注意:通过 @each 指令渲染的视图不会继承父视图的变量。如果子视图需要使用这些变量,您可以使用 @foreach@include 来代替它。

@once 指令

@once 指令允许您定义模板的一部分内容,这部分内容在每一个渲染周期中只会被计算一次。该指令在使用 堆栈 推送一段特定的 JavaScript 代码到页面的头部环境下是很有用的。例如,如果您想要在循环中渲染一个特定的 组件 ,您可能希望仅在组件渲染的首次推送 JavaScript 代码到头部:

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

构建布局

使用组件布局

大多数 web 应用程序在不同的页面上有相同的总体布局。如果我们必须在创建的每个视图中重复整个布局 HTML,那么维护我们的应用程序将变得非常麻烦和困难。谢天谢地,将此布局定义为单个 Blade component 并在整个应用程序中非常方便地使用它。

定义布局组件

例如,假设我们正在构建一个 todo list 应用程序。我们可以定义如下所示的 layout 组件:

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

<html>
    <head>
        <title>{{ $title ?? 'Todo Manager' }}</title>
    </head>
    <body>
        <h1>Todos</h1>
        <hr/>
        {{ $slot }}
    </body>
</html>

应用布局组件

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

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

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

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

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

<x-layout>
    <x-slot name="title">
        Custom Title
    </x-slot>

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

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

use App\Models\Task;

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

使用模板继承进行布局

定义一个布局

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

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

<!-- 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 显示在布局中:

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

@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')
    @parent

    <p>This is appended to the master sidebar.</p>
@endsection

@section('content')
    <p>This is my body content.</p>
@endsection

在本例中,sidebar 部分使用 @parent 指令将内容追加(而不是覆盖)到局部的侧栏位置。在呈现视图时,@parent 指令将被布局的内容替换。

技巧:与上一个示例相反,这个 sidebar 部分以 @endsection 结束,而不是以 @show 结束。@endsection 指令将只定义一个节,@show 将定义并 立即 yield 该节。

@yield 指令还接受默认值作为其第二个参数。如果要生成的节点未定义,则将呈现此内容:

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

表单

CSRF 字段

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

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

    ...
</form>

Method 字段

由于 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 指令,以便在包含多个表单的页面上检索验证错误消息:

<!-- /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

原生 PHP

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

@php
    $counter = 1;
@endphp

组件

组件和插槽为节点、布局和继承提供了类似的用法;但是,有些人可能会发现组件和插槽的思维模型更容易理解。编写组件有两种方法:基于类的组件和匿名组件。

要创建基于类的组件,可以使用 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 目录中。

手动注册扩展包的组件

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

但是,如果您正在构建一个使用 Blade 组件的扩展包,则需要手动注册组件类及其 HTML 标记别名。通常应在包的服务提供程序的 boot 方法中注册组件:

use Illuminate\Support\Facades\Blade;

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

注册组件后,可以使用其标记别名渲染组件

<x-package-alert/>

或者,可以使用 componentNamespace 方法按约定自动加载组件类。例如,Nightshade 包可能具有驻留在 package\Views\components 命名空间中的 CalendarColorPicker 组件:

use Illuminate\Support\Facades\Blade;

/**
 * 启动你的扩展包的服务
 *
 * @return void
 */
public function boot()
{
    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/>

给组件传递数据

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

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

译者注:类似于 VUE 组件语法

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

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * The alert type.
     *
     * @var string
     */
    public $type;

    /**
     * The alert message.
     *
     * @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');
    }
}

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

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

驼峰命名

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

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

$alertType 参数可以使用如下所示的方式接受数据:

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

Escaping Attribute Rendering

因为一些 JavaScript 框架,例如 Alpine.js 还可以使用冒号前缀属性,你可以使用双冒号(::)前缀通知 Blade 属性不是 PHP 表达式。例如,给定以下组件:

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

Blade 将渲染出以下 HTML 内容:

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

组件方法

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

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

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

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

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

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

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

        return '<div>Components content</div>';
    };
}

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

闭包应该返回一个字符串。如果返回的字符串与现有视图相对应,则将呈现该视图;否则,返回的字符串将作为内联 Blade 视图进行计算。

附加依赖项

如果你的组件需要引入来自 Laravel 的 服务容器 的依赖项,你可以在组件的任何数据属性之前列出这些依赖项,这些依赖项将由容器自动注入:

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

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * The alert type.
     *
     * @var string
     */
    public $type;

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

组件属性

我们已经研究了如何将数据属性传递给组件;但是,有时你可能需要指定额外的 HTML 属性,例如 class,这些属性不是组件运行所需的数据的一部分。通常,你希望将这些附加属性向下传递到组件模板的根元素。例如,假设我们要呈现一个 alert 组件,如下所示:

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

所有不属于组件构造函数的属性都将自动添加到组件的 「属性包」 中。此属性包通过 $attributes 变量自动提供给组件。所有属性都可以通过回传此变量在组件中呈现:

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

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

默认属性 / 合并属性

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

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

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

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

组件的最终呈现 HTML 将如下所示:

<div class="alert alert-error mb-4">
    <!-- Contents of the $message variable -->
</div>

有条件地合并 class

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

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

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

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

非 class 属性的合并

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

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

若要使用自定义 type 呈现按钮组件,可以在使用该组件时指定它。如果未指定 type,则将使用 button 作为 type 值:

<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 ($value, $key) => $key == 'foo') }}

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

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

使用 first 方法,可以呈现给定属性包中的第一个属性:

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

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

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

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

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

保留关键字

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

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

插槽

你通常需要通过 「插槽」 将其他内容传递给组件。通过回显 $slot 变量来呈现组件插槽。为了探索这个概念,我们假设 alert 组件具有以下内容:

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

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

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

<x-alert>
    <strong>Whoops!</strong> Something went wrong!
</x-alert>

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

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

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

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

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

<x-alert>
    <x-slot name="title">
        Server Error
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>

作用域插槽

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

<x-alert>
    <x-slot name="title">
        {{ $component->formatAlert('Server Error') }}
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>

内联组件视图

对于非常小的组件,管理组件类和组件的视图模板可能会感觉很麻烦。因此,可以直接从 render 方法返回组件的标记:

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

生成内联组件

要创建呈现内联视图的组件,可以在执行 make:component 命令的时候带上 inline

php artisan make:component Alert --inline

匿名组件

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

<x-alert/>

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

<x-inputs.button/>

数据 Properties / Attributes

由于匿名组件没有任何关联的类,你可能想知道如何区分哪些数据应该作为变量传递给组件,哪些属性应该放在组件的 属性包 中。

你可以使用组件 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"/>

动态组件

在渲染组件时,你可能需要直到运行时才知道应该渲染哪个组件。在这种情况下,可以使用 Laravel 的内置 dynamic component 组件根据运行时值或变量呈现组件:

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

手动注册组件

注意:以下有关手动注册组件的文档主要适用于编写包含视图组件的 Laravel 扩展包的人员。如果你不是在编写软件包,那么这部分组件文档可能与你无关。

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

但是,如果您正在构建一个使用 Blade 组件的扩展包或者将组件放置在非常规目录中,则需要手动注册组件类及其 HTML 标记别名,以便 Laravel 知道在哪里可以找到组件。通常应在包的服务提供程序的 boot 方法中注册组件:

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

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

你的组件一旦注册,可以使用其标记别名呈现组件:

<x-package-alert/>

自动加载扩展包的组件

或者,可以使用 componentNamespace 方法按约定自动加载组件类。例如, Nightshade 包可能具有驻留在 package\Views\components 命名空间中的 CalendarColorPicker 组件:

use Illuminate\Support\Facades\Blade;

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

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

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

Blade 将通过蛇形组件名来自动检测链接到此组件的类。子目录也支持使用 . 表示法。

堆叠

Blade 允许你推送到可以在其他视图或布局中的其他地方渲染的命名堆栈。这对于指定子视图所需的任何 JavaScript 库特别有用:

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

你可以根据需要多次推入堆栈。要呈现完整的堆栈内容,请将堆栈的名称传递给 @stack 指令:

<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>

如果要将内容前置到堆栈的开头,应使用 @prepend 指令:

@push('scripts')
    这是第二加载的...
@endpush

// Later...

@prepend('scripts')
    这是第一加载的...
@endprepend

服务注入

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

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

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

扩展 Blade

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

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

<?php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册应用的服务
     *
     * @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 echo ($var)->format('m/d/Y H:i'); ?>

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

自定义 if 声明

在定义简单的自定义条件语句时,编写自定义指令通常比较复杂。因此,Blade 提供了一个 Blade::if 方法,允许你使用闭包快速定义自定义条件指令。例如,让我们定义一个自定义条件来检查为应用程序配置的默认 「存储」。我们可以在 AppServiceProviderboot 方法中执行此操作:

use Illuminate\Support\Facades\Blade;

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

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

@disk('local')
    <!-- 应用正在使用 local 存储 ... -->
@elsedisk('s3')
    <!-- 应用正在使用 s3 存储 ... -->
@else
    <!-- 应用正在使用其他存储 ... -->
@enddisk

@unlessdisk('local')
    <!-- 应用当前没有使用 local 存储 ... -->
@enddisk

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
贡献者:10
讨论数量: 0
发起讨论 只看当前版本


暂无话题~