Skip to content
全部文档

Volt

WARNING

在 Livewire v4 中 Volt 是可选的

Livewire v4 开箱即提供单文件组件,多数应用不必使用 Volt。Volt 面向更偏好函数式、闭包语法而非基于类组件的开发者。

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 组件一样,可用 Livewire 的标签语法或 @livewire Blade 指令渲染 Volt 组件:

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());

属性

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

php
<?php

use function Livewire\Volt\{state};

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

?>

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

若 state 属性的初始值依赖外部因素(如数据库查询、模型或容器服务),应将其解析封装在闭包中,以免在真正需要之前就解析该值:

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

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

若 state 属性的初始值通过 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 中,对要保护的 state 链式调用 locked 方法即可:

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

响应式属性

使用嵌套组件时,可能需要把父组件属性传给子组件,并在父组件更新该属性时让子组件自动更新

在 Volt 中,对希望成为响应式的 state 链式调用 reactive 方法即可:

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

可绑定属性

若不使用响应式属性,Livewire 还提供可绑定(modelable)功能:可直接在子组件上用 wire:model 在父子组件间共享状态。

在 Volt 中,对希望成为可绑定的 state 链式调用 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">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 组件时,可向组件传入 lazy 参数,推迟其加载直至初始页面完全加载。默认情况下,Livewire 会在组件将要加载的位置插入 <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 方法:

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

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

// ...

分页

Livewire 与 Volt 也完整支持分页。要在函数式 Volt 组件上引入 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 组件上的「state」。从视图代理到组件的数据初始化 state 时,只需声明 state 变量名。Volt 会用代理的视图数据自动水合 state 的默认值:

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');