Skip to content

Volt

先熟悉 Livewire

在使用 Volt 之前,我们建议您先熟悉标准的、基于类的 Livewire 用法。这将帮助您快速将 Livewire 知识转化为使用 Volt 函数式 API 编写组件。

Volt 是为 Livewire 精心打造的函数式 API,支持单文件组件,允许组件的 PHP 逻辑和 Blade 模板共存于同一个文件中。在底层,函数式 API 会被编译为 Livewire 类组件,并与同一文件中的模板关联。

一个简单的 Volt 组件如下所示:

php
<?php

use function Livewire\Volt\{state};

state(['count' => 0]);

$increment = fn () => $this->count++;

?>

<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>

安装

首先,使用 Composer 包管理器将 Volt 安装到您的项目中:

bash
composer require livewire/volt

安装 Volt 后,您可以执行 volt:install Artisan 命令,该命令会将 Volt 的服务提供者文件安装到您的应用程序中。该服务提供者指定了 Volt 将在哪些挂载目录中搜索单文件组件:

bash
php artisan volt:install

创建组件

您可以通过在任何 Volt 挂载目录中放置一个带有 .blade.php 扩展名的文件来创建 Volt 组件。默认情况下,VoltServiceProvider 会挂载 resources/views/livewireresources/views/pages 目录,但您可以在 Volt 服务提供者的 boot 方法中自定义这些目录。

为了方便,您可以使用 make:volt Artisan 命令来创建一个新的 Volt 组件:

bash
php artisan make:volt counter

在生成组件时添加 --test 指令,会同时生成相应的测试文件。如果您希望相关测试使用 Pest,应使用 --pest 标志:

bash
php artisan make:volt counter --test --pest

添加 --class 指令将生成一个基于类的 Volt 组件。

bash
php artisan make:volt counter --class

API 风格

通过使用 Volt 的函数式 API,我们可以通过导入的 Livewire\Volt 函数来定义 Livewire 组件的逻辑。Volt 会将函数式代码转换并编译为传统的 Livewire 类,使我们能够以更少的样板代码充分利用 Livewire 的强大功能。

Volt 的 API 会自动将其使用的任何闭包绑定到底层组件。因此,在任何时候,操作、计算属性或监听器都可以使用 $this 变量来引用组件:

php
use function Livewire\Volt\{state};

state(['count' => 0]);

$increment = fn () => $this->count++;

// ...

基于类的 Volt 组件

如果您想在享受 Volt 单文件组件功能的同时仍然编写基于类的组件,我们也为您提供了支持。首先,定义一个扩展 Livewire\Volt\Component 的匿名类。在该类中,您可以使用传统的 Livewire 语法来使用 Livewire 的所有功能:

blade
<?php

use Livewire\Volt\Component;

new class extends Component {
    public $count = 0;

    public function increment()
    {
        $this->count++;
    }
} ?>

<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>

类属性

与典型的 Livewire 组件一样,Volt 组件也支持类属性。当使用匿名 PHP 类时,类属性应在 new 关键字之后定义:

blade
<?php

use Livewire\Attributes\{Layout, Title};
use Livewire\Volt\Component;

new
#[Layout('layouts.guest')]
#[Title('Login')]
class extends Component
{
    public string $name = '';

    // ...

提供额外的视图数据

使用基于类的 Volt 组件时,渲染的视图就是同一文件中的模板。如果您需要在每次渲染视图时向其传递额外的数据,可以使用 with 方法。除了组件的公共属性外,这些数据也会被传递给视图:

blade
<?php

use Livewire\WithPagination;
use Livewire\Volt\Component;
use App\Models\Post;

new class extends Component {
    use WithPagination;

    public function with(): array
    {
        return [
            'posts' => Post::paginate(10),
        ];
    }
} ?>

<div>
    <!-- ... -->
</div>

修改视图实例

有时,您可能希望直接与视图实例交互,例如使用翻译后的字符串设置视图的标题。为此,您可以在组件上定义一个 rendering 方法:

blade
<?php

use Illuminate\View\View;
use Livewire\Volt\Component;

new class extends Component {
    public function rendering(View $view): void
    {
        $view->title('Create Post');

        // ...
    }

    // ...

渲染和挂载组件

与典型的 Livewire 组件一样,Volt 组件可以使用 Livewire 的标签语法或 @livewire Blade 指令来渲染:

blade
<livewire:user-index :users="$users" />

要声明组件接受的属性,可以使用 state 函数:

php
use function Livewire\Volt\{state};

state('users');

// ...

如果需要,您可以通过向 state 函数提供一个闭包来拦截传递给组件的属性,从而能够与给定的值交互并修改它:

php
use function Livewire\Volt\{state};

state(['count' => fn ($users) => count($users)]);

mount 函数可用于定义 Livewire 组件的 "mount" 生命周期钩子。提供给组件的参数将被注入到该方法中。mount 钩子所需的任何其他参数将由 Laravel 的服务容器解析:

php
use App\Services\UserCounter;
use function Livewire\Volt\{mount};

mount(function (UserCounter $counter, $users) {
    $counter->store('userCount', count($users));

    // ...
});

全页面组件

您可以选择通过在应用程序的 routes/web.php 文件中定义 Volt 路由,将 Volt 组件渲染为全页面组件:

php
use Livewire\Volt\Volt;

Volt::route('/users', 'user-index');

默认情况下,组件将使用 components.layouts.app 布局来渲染。您可以使用 layout 函数来自定义此布局文件:

php
use function Livewire\Volt\{layout, state};

state('users');

layout('components.layouts.admin');

// ...

您还可以使用 title 函数来自定义页面的标题:

php
use function Livewire\Volt\{layout, state, title};

state('users');

layout('components.layouts.admin');

title('Users');

// ...

如果标题依赖于组件状态或外部依赖项,您可以向 title 函数传递一个闭包:

php
use function Livewire\Volt\{layout, state, title};

state('users');

layout('components.layouts.admin');

title(fn () => 'Users: ' . $this->users->count());

属性

Volt 属性与 Livewire 属性一样,可以在视图中方便地访问,并在 Livewire 更新之间持久化。您可以使用 state 函数来定义属性:

php
<?php

use function Livewire\Volt\{state};

state(['count' => 0]);

?>

<div>
    {{ $count }}
</div>

如果状态属性的初始值依赖于外部依赖项(如数据库查询、模型或容器服务),应将其解析封装在闭包中。这可以防止在绝对必要之前解析该值:

php
use App\Models\User;
use function Livewire\Volt\{state};

state(['count' => fn () => User::count()]);

如果状态属性的初始值是通过 Laravel Folio 的路由模型绑定注入的,也应将其封装在闭包中:

php
use App\Models\User;
use function Livewire\Volt\{state};

state(['user' => fn () => $user]);

当然,属性也可以在不明确指定其初始值的情况下声明。在这种情况下,其初始值将为 null,或者根据渲染组件时传递的属性进行设置:

php
use function Livewire\Volt\{mount, state};

state(['count']);

mount(function ($users) {
    $this->count = count($users);

    //
});

锁定属性

Livewire 提供了通过“锁定”属性来保护它们的能力,从而防止在客户端发生任何修改。要在 Volt 中实现这一点,只需在您希望保护的状态上链式调用 locked 方法:

php
state(['id'])->locked();

响应式属性

处理嵌套组件时,您可能会遇到需要将属性从父组件传递给子组件,并且当父组件更新该属性时子组件能够自动更新的情况。

要在 Volt 中实现这一点,可以在您希望响应式的状态上链式调用 reactive 方法:

php
state(['todos'])->reactive();

可模型化属性

在不想使用响应式属性的情况下,Livewire 提供了一个可模型化功能,您可以直接在子组件上使用 wire:model 在父组件和子组件之间共享状态。

要在 Volt 中实现这一点,只需在您希望可模型化的状态上链式调用 modelable 方法:

php
state(['form'])->modelable();

计算属性

Livewire 还允许您定义计算属性,这对于懒加载组件所需信息非常有用。计算属性的结果会在单个 Livewire 请求生命周期中被“记忆化”或缓存在内存中。

要定义计算属性,可以使用 computed 函数。变量的名称将决定计算属性的名称:

php
<?php

use App\Models\User;
use function Livewire\Volt\{computed};

$count = computed(function () {
    return User::count();
});

?>

<div>
    {{ $this->count }}
</div>

您可以通过在计算属性定义上链式调用 persist 方法,将计算属性的值持久化到应用程序的缓存中:

php
$count = computed(function () {
    return User::count();
})->persist();

默认情况下,Livewire 会将计算属性的值缓存 3600 秒。您可以通过向 persist 方法提供所需的秒数来自定义此值:

php
$count = computed(function () {
    return User::count();
})->persist(seconds: 10);

操作

Livewire 操作提供了一种方便的方式来监听页面交互并调用组件上的相应方法,从而重新渲染组件。通常,操作是响应用户点击按钮而被调用的。

要使用 Volt 定义 Livewire 操作,您只需定义一个闭包。包含闭包的变量名称将决定操作的名称:

php
<?php

use function Livewire\Volt\{state};

state(['count' => 0]);

$increment = fn () => $this->count++;

?>

<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>

在闭包内,$this 变量绑定到底层的 Livewire 组件,使您能够像在典型 Livewire 组件中一样访问组件上的其他方法:

php
use function Livewire\Volt\{state};

state(['count' => 0]);

$increment = function () {
    $this->dispatch('count-updated');

    //
};

您的操作还可以从 Laravel 的服务容器接收参数或依赖项:

php
use App\Repositories\PostRepository;
use function Livewire\Volt\{state};

state(['postId']);

$delete = function (PostRepository $posts) {
    $posts->delete($this->postId);

    // ...
};

无渲染操作

在某些情况下,您的组件可能会声明一个不执行任何会导致组件渲染的 Blade 模板发生变化的操作。如果是这种情况,您可以通过将操作封装在 action 函数中并在其定义上链式调用 renderless 方法来跳过渲染阶段

php
use function Livewire\Volt\{action};

$incrementViewCount = action(fn () => $this->viewCount++)->renderless();

受保护的助手函数

默认情况下,所有 Volt 操作都是“公共的”,可以由客户端调用。如果您希望创建一个仅从您的操作内部可访问的函数,可以使用 protect 函数:

php
use App\Repositories\PostRepository;
use function Livewire\Volt\{protect, state};

state(['postId']);

$delete = function (PostRepository $posts) {
    $this->ensurePostCanBeDeleted();

    $posts->delete($this->postId);

    // ...
};

$ensurePostCanBeDeleted = protect(function () {
    // ...
});

表单

Livewire 的表单提供了一种方便的方式,可以在单个类中处理表单验证和提交。要在 Volt 组件中使用 Livewire 表单,可以使用 form 函数:

php
<?php

use App\Livewire\Forms\PostForm;
use function Livewire\Volt\{form};

form(PostForm::class);

$save = function () {
    $this->form->store();

    // ...
};

?>

<form wire:submit="save">
    <input type="text" wire:model="form.title">
    @error('form.title') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">Save</button>
</form>

如您所见,form 函数接受 Livewire 表单类的名称。一旦定义,就可以通过组件内的 $this->form 属性访问该表单。

如果您想为表单使用不同的属性名称,可以将名称作为第二个参数传递给 form 函数:

php
form(PostForm::class, 'postForm');

$save = function () {
    $this->postForm->store();

    // ...
};

监听器

Livewire 的全局事件系统支持组件之间的通信。如果页面上存在两个 Livewire 组件,它们可以通过使用事件和监听器进行通信。使用 Volt 时,可以使用 on 函数定义监听器:

php
use function Livewire\Volt\{on};

on(['eventName' => function () {
    //
}]);

如果您需要为事件监听器分配动态名称,例如基于已认证用户或传递给组件的数据,可以向 on 函数传递一个闭包。此闭包可以接收任何组件参数,以及将通过 Laravel 服务容器解析的其他依赖项:

php
on(fn ($post) => [
    'event-'.$post->id => function () {
        //
    }),
]);

为了方便起见,在定义监听器时还可以使用"点"表示法引用组件数据:

php
on(['event-{post.id}' => function () {
    //
}]);

生命周期钩子

Livewire 有多种生命周期钩子,可用于在组件生命周期的不同阶段执行代码。使用 Volt 的便捷 API,您可以使用相应的函数来定义这些生命周期钩子:

php
use function Livewire\Volt\{boot, booted, ...};

boot(fn () => /* ... */);
booted(fn () => /* ... */);
mount(fn () => /* ... */);
hydrate(fn () => /* ... */);
hydrate(['count' => fn () => /* ... */]);
dehydrate(fn () => /* ... */);
dehydrate(['count' => fn () => /* ... */]);
updating(['count' => fn () => /* ... */]);
updated(['count' => fn () => /* ... */]);

懒加载占位符

渲染 Livewire 组件时,您可以向 Livewire 组件传递 lazy 参数,以延迟其加载,直到初始页面完全加载完成。默认情况下,Livewire 会在 DOM 中将要加载组件的位置插入 <div></div> 标签。

如果您想自定义在初始页面加载时组件占位符内显示的 HTML,可以使用 placeholder 函数:

php
use function Livewire\Volt\{placeholder};

placeholder('<div>Loading...</div>');

验证

Livewire 提供了对 Laravel 强大验证功能的便捷访问。使用 Volt 的 API,您可以使用 rules 函数定义组件的验证规则。与传统的 Livewire 组件一样,当您调用 validate 方法时,这些规则将应用于您的组件数据:

php
<?php

use function Livewire\Volt\{rules};

rules(['name' => 'required|min:6', 'email' => 'required|email']);

$submit = function () {
    $this->validate();

    // ...
};

?>

<form wire:submit.prevent="submit">
    //
</form>

如果您需要动态定义规则,例如基于已认证用户或数据库信息的规则,可以向 rules 函数提供一个闭包:

php
rules(fn () => [
    'name' => ['required', 'min:6'],
    'email' => ['required', 'email', 'not_in:'.Auth::user()->email]
]);

错误消息和属性

要修改验证期间使用的验证消息或属性,可以在 rules 定义上链式调用 messagesattributes 方法:

php
use function Livewire\Volt\{rules};

rules(['name' => 'required|min:6', 'email' => 'required|email'])
    ->messages([
        'email.required' => 'The :attribute may not be empty.',
        'email.email' => 'The :attribute format is invalid.',
    ])->attributes([
        'email' => 'email address',
    ]);

文件上传

使用 Volt 时,由于 Livewire 的支持,上传和存储文件变得更加容易。要在函数式 Volt 组件上包含 Livewire\WithFileUploads trait,可以使用 usesFileUploads 函数:

php
use function Livewire\Volt\{state, usesFileUploads};

usesFileUploads();

state(['photo']);

$save = function () {
    $this->validate([
        'photo' => 'image|max:1024',
    ]);

    $this->photo->store('photos');
};

URL 查询参数

有时在组件状态更改时更新浏览器的 URL 查询参数很有用。在这些情况下,您可以使用 url 方法指示 Livewire 将 URL 查询参数与组件状态的一部分同步:

php
<?php

use App\Models\Post;
use function Livewire\Volt\{computed, state};

state(['search'])->url();

$posts = computed(function () {
    return Post::where('title', 'like', '%'.$this->search.'%')->get();
});

?>

<div>
    <input wire:model.live="search" type="search" placeholder="Search posts by title...">

    <h1>Search Results:</h1>

    <ul>
        @foreach($this->posts as $post)
            <li wire:key="{{ $post->id }}">{{ $post->title }}</li>
        @endforeach
    </ul>
</div>

Livewire 支持的其他 URL 查询参数选项(例如 URL 查询参数别名)也可以提供给 url 方法:

php
use App\Models\Post;
use function Livewire\Volt\{state};

state(['page' => 1])->url(as: 'p', history: true, keep: true);

// ...

分页

Livewire 和 Volt 还完全支持分页。要在函数式 Volt 组件上包含 Livewire 的 Livewire\WithPagination trait,可以使用 usesPagination 函数:

php
<?php

use function Livewire\Volt\{with, usesPagination};

usesPagination();

with(fn () => ['posts' => Post::paginate(10)]);

?>

<div>
    @foreach ($posts as $post)
        //
    @endforeach

    {{ $posts->links() }}
</div>

与 Laravel 一样,Livewire 的默认分页视图使用 Tailwind 类进行样式设置。如果您在应用程序中使用 Bootstrap,可以在调用 usesPagination 函数时指定所需的主题来启用 Bootstrap 分页主题:

php
usesPagination(theme: 'bootstrap');

自定义 trait 和接口

要在函数式 Volt 组件上包含任何任意的 trait 或接口,可以使用 uses 函数:

php
use function Livewire\Volt\{uses};

use App\Contracts\Sorting;
use App\Concerns\WithSorting;

uses([Sorting::class, WithSorting::class]);

匿名组件

有时,您可能希望将页面的一小部分转换为 Volt 组件,而无需将其提取到单独的文件中。例如,假设有一个返回以下视图的 Laravel 路由:

php
Route::get('/counter', fn () => view('pages/counter.blade.php'));

该视图的内容是一个典型的 Blade 模板,包括布局定义和插槽。但是,通过将视图的一部分包装在 @volt Blade 指令中,我们可以将该部分视图转换为功能完整的 Volt 组件:

php
<?php

use function Livewire\Volt\{state};

state(['count' => 0]);

$increment = fn () => $this->count++;

?>

<x-app-layout>
    <x-slot name="header">
        Counter
    </x-slot>

    @volt('counter')
        <div>
            <h1>{{ $count }}</h1>
            <button wire:click="increment">+</button>
        </div>
    @endvolt
</x-app-layout>

向匿名组件传递数据

渲染包含匿名组件的视图时,提供给视图的所有数据也将对匿名 Volt 组件可用:

php
use App\Models\User;

Route::get('/counter', fn () => view('users.counter', [
    'count' => User::count(),
]));

当然,您可以在 Volt 组件上将这些数据声明为"状态"。当从视图代理到组件的数据初始化状态时,您只需声明状态变量的名称。Volt 将使用代理的视图数据自动填充状态的默认值:

php
<?php

use function Livewire\Volt\{state};

state('count');

$increment = function () {
    // Store the new count value in the database...

    $this->count++;
};

?>

<x-app-layout>
    <x-slot name="header">
        Initial value: {{ $count }}
    </x-slot>

    @volt('counter')
        <div>
            <h1>{{ $count }}</h1>
            <button wire:click="increment">+</button>
        </div>
    @endvolt
</x-app-layout>

测试组件

要开始测试 Volt 组件,可以调用 Volt::test 方法,并提供组件的名称:

php
use Livewire\Volt\Volt;

it('increments the counter', function () {
    Volt::test('counter')
        ->assertSee('0')
        ->call('increment')
        ->assertSee('1');
});

测试 Volt 组件时,您可以使用标准 Livewire 测试 API 提供的所有方法。

如果您的 Volt 组件是嵌套的,可以使用"点"表示法来指定要测试的组件:

php
Volt::test('users.stats')

测试包含匿名 Volt 组件的页面时,可以使用 assertSeeVolt 方法来断言组件已被渲染:

php
$this->get('/users')
    ->assertSeeVolt('stats');