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 方法来跳过 Livewire 生命周期的渲染阶段

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">保存</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>加载中...</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' => ':attribute 不能为空。',
        'email.email' => ':attribute 格式无效。',
    ])->attributes([
        'email' => '邮箱地址',
    ]);

文件上传

使用 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="按标题搜索文章...">

    <h1>搜索结果:</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">
        计数器
    </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 () {
    // 将新的计数值存储到数据库...

    $this->count++;
};

?>

<x-app-layout>
    <x-slot name="header">
        初始值: {{ $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');