Volt
在使用 Volt 之前,我们建议您熟悉基于类的标准 Livewire 用法。这将使您能够快速将 Livewire 知识转移到使用 Volt 的函数式 API 编写组件中。
Volt 是 Livewire 的优雅函数式 API,支持单文件组件,允许组件的 PHP 逻辑和 Blade 模板共存于同一文件中。在幕后,函数式 API 被编译为 Livewire 类组件,并与同一文件中存在的模板链接。
一个简单的 Volt 组件如下所示
<?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 安装到您的项目中
composer require livewire/volt
安装 Volt 后,您可以执行 volt:install
Artisan 命令,该命令会将 Volt 的服务提供程序文件安装到您的应用程序中。此服务提供程序指定了 Volt 将搜索单文件组件的已挂载目录
php artisan volt:install
创建组件
您可以在任何 Volt 挂载目录中放置一个扩展名为 .blade.php
的文件来创建 Volt 组件。默认情况下,VoltServiceProvider
挂载 resources/views/livewire
和 resources/views/pages
目录,但您可以在 Volt 服务提供程序的 boot
方法中自定义这些目录。
为了方便,你可以使用 make:volt
Artisan 命令来创建一个新的 Volt 组件
php artisan make:volt counter
在生成组件时添加 --test
指令,将同时生成一个对应的测试文件。如果你想让关联的测试使用 Pest,你应该使用 --pest
标志
php artisan make:volt counter --test --pest
通过添加 --class
指令,它将生成一个基于类的 Volt 组件。
php artisan make:volt counter --class
API 样式
通过使用 Volt 的函数式 API,我们可以通过导入的 Livewire\Volt
函数来定义 Livewire 组件的逻辑。然后,Volt 将函数式代码转换并编译成一个传统的 Livewire 类,使我们能够利用 Livewire 的强大功能,同时减少样板代码。
Volt 的 API 会自动将它使用的任何闭包绑定到底层组件。因此,在任何时候,动作、计算属性或侦听器都可以使用 $this
变量引用组件
use function Livewire\Volt\{state}; state(['count' => 0]); $increment = fn () => $this->count++; // ...
基于类的 Volt 组件
如果你想享受 Volt 的单文件组件功能,同时仍然编写基于类的组件,我们已经为你准备好了。要开始,定义一个匿名类,它扩展 Livewire\Volt\Component
。在类中,你可以使用传统的 Livewire 语法利用 Livewire 的所有特性
<?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
关键字之后定义
<?php use Livewire\Attributes\{Layout, Title};use Livewire\Volt\Component; new#[Layout('layouts.guest')]#[Title('Login')]class extends Component{ public string $name = ''; // ...
提供额外的视图数据
在使用基于类的 Volt 组件时,渲染的视图是同一文件中存在的模板。如果你需要在每次渲染视图时传递额外的数据,你可以使用 with
方法。这些数据将传递给视图,作为组件的公共属性的补充
<?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
方法
<?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 指令来渲染
<livewire:user-index :users="$users" />
要声明组件接受的属性,你可以使用 state
函数
use function Livewire\Volt\{state}; state('users'); // ...
如有必要,你可以通过向 state
函数提供一个闭包来拦截传递给组件的属性,这允许你与给定值交互并修改它
use function Livewire\Volt\{state}; state(['count' => fn ($users) => count($users)]);
mount
函数可用于定义 Livewire 组件的“mount”生命周期挂钩。提供给组件的参数将注入到此方法中。mount 挂钩所需的任何其他参数都将由 Laravel 的服务容器解析
use App\Services\UserCounter;use function Livewire\Volt\{mount}; mount(function (UserCounter $counter, $users) { $counter->store('userCount', count($users)); // ...});
全页组件
或者,你可以通过在应用程序的 routes/web.php
文件中定义一个 Volt 路由,将 Volt 组件渲染为全页组件
use Livewire\Volt\Volt; Volt::route('/users', 'user-index');
默认情况下,组件将使用 components.layouts.app
布局进行渲染。你可以使用 layout
函数自定义此布局文件
use function Livewire\Volt\{layout, state}; state('users'); layout('components.layouts.admin'); // ...
你还可以使用 title
函数自定义页面的标题
use function Livewire\Volt\{layout, state, title}; state('users'); layout('components.layouts.admin'); title('Users'); // ...
如果标题依赖于组件状态或外部依赖项,你可以将闭包传递给 title
函数
use function Livewire\Volt\{layout, state, title}; state('users'); layout('components.layouts.admin'); title(fn () => 'Users: ' . $this->users->count());
属性
Volt 属性与 Livewire 属性类似,可以在视图中方便地访问,并在 Livewire 更新之间保持不变。你可以使用 state
函数定义属性
<?php use function Livewire\Volt\{state}; state(['count' => 0]); ?> <div> {{ $count }}</div>
如果状态属性的初始值依赖于外部依赖项,例如数据库查询、模型或容器服务,则应将其解析封装在闭包中。这可以防止在绝对必要之前解析值
use App\Models\User;use function Livewire\Volt\{state}; state(['count' => fn () => User::count()]);
如果状态属性的初始值通过 Laravel Folio 的路由模型绑定注入,也应将其封装在闭包中
use App\Models\User;use function Livewire\Volt\{state}; state(['user' => fn () => $user]);
当然,也可以在不显式指定其初始值的情况下声明属性。在这种情况下,它们的初始值将为 null
,或将基于在组件渲染时传递给组件的属性进行设置
use function Livewire\Volt\{mount, state}; state(['count']); mount(function ($users) { $this->count = count($users); //});
锁定属性
Livewire 提供了保护属性的功能,允许你“锁定”它们,从而防止在客户端进行任何修改。要使用 Volt 实现此目的,只需在要保护的状态上链接 locked
方法即可
state(['id'])->locked();
响应式属性
在使用嵌套组件时,您可能会遇到需要将属性从父组件传递给子组件的情况,并且当父组件更新属性时,子组件自动更新。
要使用 Volt 实现此目的,您可以将 reactive
方法链接到您希望响应的状态
state(['todos'])->reactive();
计算属性
Livewire 还允许您定义计算属性,这对于延迟获取组件所需的信息非常有用。计算属性结果对于单个 Livewire 请求生命周期来说是“备忘的”或缓存在内存中的。
要定义计算属性,您可以使用 computed
函数。变量的名称将决定计算属性的名称
<?php use App\Models\User;use function Livewire\Volt\{computed}; $count = computed(function () { return User::count();}); ?> <div> {{ $this->count }}</div>
您可以通过将 persist
方法链接到计算属性定义来将计算属性的值保存在应用程序的缓存中
$count = computed(function () { return User::count();})->persist();
默认情况下,Livewire 会将计算属性的值缓存 3600 秒。您可以通过向 persist
方法提供所需秒数来自定义此值
$count = computed(function () { return User::count();})->persist(seconds: 10);
操作
Livewire 操作提供了一种便捷的方式来侦听页面交互并调用组件上的相应方法,从而重新渲染组件。通常,操作是在响应用户单击按钮时调用的。
要使用 Volt 定义 Livewire 操作,您只需定义一个闭包。包含闭包的变量的名称将决定操作的名称
<?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 组件中一样访问组件上的其他方法
use function Livewire\Volt\{state}; state(['count' => 0]); $increment = function () { $this->dispatch('count-updated'); //};
您的操作还可以从 Laravel 的服务容器接收参数或依赖项
use App\Repositories\PostRepository;use function Livewire\Volt\{state}; state(['postId']); $delete = function (PostRepository $posts) { $posts->delete($this->postId); // ...};
无渲染操作
在某些情况下,组件可能声明一个操作,该操作不会执行任何导致组件渲染的 Blade 模板发生更改的操作。如果是这种情况,你可以通过将操作封装在 action
函数中,并将 renderless
方法链接到其定义中,来跳过 Livewire 生命周期中的渲染阶段
use function Livewire\Volt\{action}; $incrementViewCount = action(fn () => $this->viewCount++)->renderless();
受保护的帮助器
默认情况下,所有 Volt 操作都是“公共”的,并且可以由客户端调用。如果你希望创建一个仅可在操作中访问的函数,则可以使用 protect
函数
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 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
函数
form(PostForm::class, 'postForm'); $save = function () { $this->postForm->store(); // ...};
侦听器
Livewire 的全局事件系统支持组件之间的通信。如果页面上存在两个 Livewire 组件,它们可以通过利用事件和侦听器进行通信。使用 Volt 时,可以使用 on
函数定义侦听器
use function Livewire\Volt\{on}; on(['eventName' => function () { //}]);
如果你需要为事件侦听器分配动态名称,例如基于经过身份验证的用户或传递给组件的数据,则可以将闭包传递给 on
函数。此闭包可以接收任何组件参数,以及将通过 Laravel 服务容器解析的其他依赖项
on(fn ($post) => [ 'event-'.$post->id => function () { // }),]);
为了方便起见,在使用“点”表示法定义侦听器时也可以引用组件数据
on(['event-{post.id}' => function () { //}]);
生命周期钩子
Livewire 有各种生命周期钩子,可以在组件生命周期的各个点执行代码。使用 Volt 的便捷 API,你可以使用相应的功能定义这些生命周期钩子
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 组件,以 延迟其加载,直到初始页面完全加载。默认情况下,Livewire 会在 DOM 中插入 <div></div>
标记,组件将在其中加载。
如果你想自定义在初始页面加载时显示在组件占位符中的 HTML,你可以使用 placeholder
函数
use function Livewire\Volt\{placeholder}; placeholder('<div>Loading...</div>');
验证
Livewire 可以轻松访问 Laravel 强大的 验证功能。使用 Volt 的 API,你可以使用 rules
函数定义组件的验证规则。与传统的 Livewire 组件一样,当你调用 validate
方法时,这些规则将应用于你的组件数据
<?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
函数提供一个闭包
rules(fn () => [ 'name' => ['required', 'min:6'], 'email' => ['required', 'email', 'not_in:'.Auth::user()->email]]);
错误消息和属性
要修改验证期间使用的验证消息或属性,你可以将 messages
和 attributes
方法链接到你的 rules
定义
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,上传和存储文件变得更加容易。要将 Livewire\WithFileUploads
特性包含在你的函数式 Volt 组件中,你可以使用 usesFileUploads
函数
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 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
方法
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
特征,可以使用 usesPagination
函数
<?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 分页主题
usesPagination(theme: 'bootstrap');
自定义特征和接口
要在功能 Volt 组件中包含任何任意特征或接口,可以使用 uses
函数
use function Livewire\Volt\{uses}; use App\Contracts\Sorting;use App\Concerns\WithSorting; uses([Sorting::class, WithSorting::class]);
匿名组件
有时,可能希望将页面的一小部分转换为 Volt 组件,而无需将其提取到单独的文件中。例如,设想一个返回以下视图的 Laravel 路由
Route::get('/counter', fn () => view('pages/counter.blade.php'));
视图的内容是一个典型的 Blade 模板,包括布局定义和插槽。但是,通过将视图的一部分包装在 @volt
Blade 指令中,我们可以将视图的那一部分转换为一个功能齐全的 Volt 组件
<?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 组件
use App\Models\User; Route::get('/counter', fn () => view('users.counter', [ 'count' => User::count(),]));
当然,可以在 Volt 组件上将此数据声明为“状态”。从视图代理到组件的数据初始化状态时,只需声明状态变量的名称即可。Volt 将使用代理视图数据自动注入状态的默认值
<?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
方法,提供组件的名称
use Livewire\Volt\Volt; it('increments the counter', function () { Volt::test('counter') ->assertSee('0') ->call('increment') ->assertSee('1');});
在测试 Volt 组件时,你可以使用标准 Livewire 测试 API 提供的所有方法。
如果你的 Volt 组件是嵌套的,你可以使用“点”符号来指定要测试的组件
Volt::test('users.stats')
在测试包含匿名 Volt 组件的页面时,你可以使用 assertSeeVolt
方法来断言该组件已渲染
$this->get('/users') ->assertSeeVolt('stats');