主题
表单
因为表单是大多数 Web 应用程序的支柱,Livewire 提供了大量有用的工具来构建它们。从处理简单的输入元素到复杂的事情如实时验证或文件上传,Livewire 有简单、文档完善的工具来让你的生活更轻松并取悦你的用户。
让我们开始吧。
提交表单
让我们从查看 post.create 组件中一个非常简单的表单开始。这个表单将有两个简单的文本输入和一个提交按钮,以及一些后端代码来管理表单的状态和提交:
php
<?php // resources/views/components/post/⚡create.blade.php
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public $title = '';
public $content = '';
public function save()
{
Post::create(
$this->only(['title', 'content'])
);
session()->flash('status', 'Post successfully updated.');
return $this->redirect('/posts');
}
};
?>
<form wire:submit="save">
<input type="text" wire:model="title">
<input type="text" wire:model="content">
<button type="submit">Save</button>
</form>如你所见,我们在上面的表单中使用 wire:model "绑定"公共 $title 和 $content 属性。这是 Livewire 最常用和最强大的功能之一。
除了绑定 $title 和 $content,我们还使用 wire:submit 在点击"Save"按钮时捕获 submit 事件并调用 save() 操作。此操作将把表单输入持久化到数据库。
在数据库中创建新帖子后,我们将用户重定向到帖子页面并向他们显示一条"flash"消息,表明新帖子已创建。
添加验证
为了避免存储不完整或危险的用户输入,大多数表单需要某种形式的输入验证。
Livewire 通过在要验证的属性上方添加 #[Validate] 属性使验证表单变得简单。
一旦属性附加了 #[Validate] 属性,验证规则将在每次服务器端更新属性值时应用。
让我们在 post.create 组件中的 $title 和 $content 属性上添加一些基本验证规则:
php
<?php // resources/views/components/post/⚡create.blade.php
use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
#[Validate('required')]
public $title = '';
#[Validate('required')]
public $content = '';
public function save()
{
$this->validate();
Post::create(
$this->only(['title', 'content'])
);
return $this->redirect('/posts');
}
};我们还将修改 Blade 模板以在页面上显示任何验证错误。
blade
<form wire:submit="save">
<input type="text" wire:model="title">
<div>
@error('title') <span class="error">{{ $message }}</span> @enderror
</div>
<input type="text" wire:model="content">
<div>
@error('content') <span class="error">{{ $message }}</span> @enderror
</div>
<button type="submit">Save</button>
</form>现在,如果用户尝试提交表单而没有填写任何字段,他们将看到验证消息告诉他们在保存帖子之前哪些字段是必需的。
Livewire 还提供了更多验证功能。有关更多信息,请访问我们关于验证的专用文档页面。
提取表单对象
如果你正在处理一个大型表单,并且希望将其所有属性、验证逻辑等提取到一个单独的类中,Livewire 提供了表单对象。
表单对象允许你跨组件重用表单逻辑,并通过将所有与表单相关的代码分组到一个单独的类中来保持组件类更干净。
你可以手动创建表单类或使用方便的 artisan 命令:
shell
php artisan livewire:form PostForm上述命令将创建一个名为 app/Livewire/Forms/PostForm.php 的文件。
让我们重写 post.create 组件以使用 PostForm 类:
php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
}php
<?php // resources/views/components/post/⚡create.blade.php
use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public PostForm $form;
public function save()
{
$this->validate();
Post::create(
$this->form->only(['title', 'content'])
);
return $this->redirect('/posts');
}
};blade
<form wire:submit="save">
<input type="text" wire:model="form.title">
<div>
@error('form.title') <span class="error">{{ $message }}</span> @enderror
</div>
<input type="text" wire:model="form.content">
<div>
@error('form.content') <span class="error">{{ $message }}</span> @enderror
</div>
<button type="submit">Save</button>
</form>如果你愿意,你也可以将帖子创建逻辑提取到表单对象中,如下所示:
php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use App\Models\Post;
use Livewire\Form;
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
public function store()
{
$this->validate();
Post::create($this->only(['title', 'content']));
}
}现在你可以从组件调用 $this->form->store():
php
<?php // resources/views/components/post/⚡create.blade.php
use App\Livewire\Forms\PostForm;
use Livewire\Component;
new class extends Component {
public PostForm $form;
public function save()
{
$this->form->store();
return $this->redirect('/posts');
}
// ...
};如果你想将此表单对象用于创建和更新表单,你可以轻松地调整它以处理两种用例。
以下是如何将同一个表单对象用于 post.edit 组件并用初始数据填充它:
php
<?php // resources/views/components/post/⚡edit.blade.php
use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public PostForm $form;
public function mount(Post $post)
{
$this->form->setPost($post);
}
public function save()
{
$this->form->update();
return $this->redirect('/posts');
}
};php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
use App\Models\Post;
class PostForm extends Form
{
public ?Post $post;
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
public function setPost(Post $post)
{
$this->post = $post;
$this->title = $post->title;
$this->content = $post->content;
}
public function store()
{
$this->validate();
Post::create($this->only(['title', 'content']));
}
public function update()
{
$this->validate();
$this->post->update(
$this->only(['title', 'content'])
);
}
}如你所见,我们在 PostForm 对象中添加了一个 setPost() 方法,可选地允许用现有数据填充表单以及将帖子存储在表单对象上供以后使用。我们还添加了一个 update() 方法来更新现有帖子。
使用 Livewire 时不需要表单对象,但它们确实为保持组件免于重复样板代码提供了很好的抽象。
重置表单字段
如果你正在使用表单对象,你可能想在提交后重置表单。这可以通过调用 reset() 方法来完成:
php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use App\Models\Post;
use Livewire\Form;
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
// ...
public function store()
{
$this->validate();
Post::create($this->only(['title', 'content']));
$this->reset();
}
}你也可以通过将属性名称传递给 reset() 方法来重置特定属性:
php
$this->reset('title');
// 或一次重置多个...
$this->reset(['title', 'content']);拉取表单字段
或者,你可以使用 pull() 方法在一次操作中检索表单的属性并重置它们。
php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use App\Models\Post;
use Livewire\Form;
class PostForm extends Form
{
#[Validate('required|min:5')]
public $title = '';
#[Validate('required|min:5')]
public $content = '';
// ...
public function store()
{
$this->validate();
Post::create(
$this->pull()
);
}
}你也可以通过将属性名称传递给 pull() 方法来拉取特定属性:
php
// 在重置前返回一个值...
$this->pull('title');
// 在重置前返回属性的键值数组...
$this->pull(['title', 'content']);使用 Rule 对象
如果你有更复杂的验证场景,需要使用 Laravel 的 Rule 对象,你可以定义一个 rules() 方法来声明你的验证规则,如下所示:
php
<?php
namespace App\Livewire\Forms;
use Illuminate\Validation\Rule;
use App\Models\Post;
use Livewire\Form;
class PostForm extends Form
{
public ?Post $post;
public $title = '';
public $content = '';
protected function rules()
{
return [
'title' => [
'required',
Rule::unique('posts')->ignore($this->post),
],
'content' => 'required|min:5',
];
}
// ...
public function update()
{
$this->validate();
$this->post->update($this->only(['title', 'content']));
$this->reset();
}
}当使用 rules() 方法而不是 #[Validate] 时,Livewire 只会在你调用 $this->validate() 时运行验证规则,而不是每次属性更新时。
如果你正在使用实时验证或任何其他场景,你希望 Livewire 在每次请求后验证特定字段,你可以使用没有提供规则的 #[Validate],如下所示:
php
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Illuminate\Validation\Rule;
use App\Models\Post;
use Livewire\Form;
class PostForm extends Form
{
public ?Post $post;
#[Validate]
public $title = '';
public $content = '';
protected function rules()
{
return [
'title' => [
'required',
Rule::unique('posts')->ignore($this->post),
],
'content' => 'required|min:5',
];
}
// ...
public function update()
{
$this->validate();
$this->post->update($this->only(['title', 'content']));
$this->reset();
}
}现在如果 $title 属性在表单提交之前被更新——比如使用 wire:model.blur 时——$title 的验证将被运行。
显示加载指示器
默认情况下,Livewire 在提交表单时会自动禁用提交按钮并将输入标记为 readonly,防止用户在处理第一次提交时再次提交表单。
但是,对于用户来说,如果没有在应用程序 UI 中提供额外的提示,可能很难检测到这种"加载"状态。
以下是通过 wire:loading 向"Save"按钮添加一个小加载旋转器的示例,以便用户了解表单正在提交:
blade
<button type="submit">
Save
<div wire:loading>
<svg>...</svg> <!-- SVG 加载旋转器 -->
</div>
</button>或者,你可以使用 Tailwind 和 Livewire 的自动 data-loading 属性来获得更简洁的标记:
blade
<button type="submit">
<span class="in-data-loading:hidden">Save</span>
<span class="not-in-data-loading:hidden">
<svg>...</svg> <!-- SVG 加载旋转器 -->
</span>
</button>实时更新字段
默认情况下,Livewire 只在表单提交时(或调用任何其他操作时)发送网络请求,而不是在填写表单时。
以 post.create 组件为例。如果你想确保"title"输入字段在用户输入时与后端的 $title 属性同步,你可以向 wire:model 添加 .live 修饰符,如下所示:
blade
<input type="text" wire:model.live="title">现在,当用户在此字段中输入时,网络请求将发送到服务器以更新 $title。这对于像实时搜索这样的事情很有用,其中在用户输入搜索框时过滤数据集。
仅在 blur 时更新字段
对于大多数情况,wire:model.live 对于实时表单字段更新来说是可以的;但是,对于文本输入来说,它可能会过于网络资源密集。
如果你不想在用户输入时发送网络请求,而是想在用户"tab"离开文本输入时发送请求(也称为"blur"输入),你可以使用 .blur 修饰符:
blade
<input type="text" wire:model.blur="title" >现在服务器上的组件类不会被更新,直到用户按下 tab 或点击离开文本输入。
实时验证
有时,你可能希望在用户填写表单时显示验证错误。这样,他们会及早收到提醒,而不是等到整个表单填写完毕。
Livewire 会自动处理这类事情。通过在 wire:model 上使用 .live 或 .blur,Livewire 将在用户填写表单时发送网络请求。每个网络请求都将在更新每个属性之前运行适当的验证规则。如果验证失败,属性将不会在服务器上更新,验证消息将显示给用户:
blade
<input type="text" wire:model.blur="title">
<div>
@error('title') <span class="error">{{ $message }}</span> @enderror
</div>php
#[Validate('required|min:5')]
public $title = '';现在,如果用户只在"title"输入中输入三个字符,然后点击表单中的下一个输入,将显示验证消息告诉他们该字段有五个字符的最小限制。
有关更多信息,请查看验证文档页面。
实时表单保存
如果你想在用户填写表单时自动保存表单,而不是等待用户点击"submit",你可以使用 Livewire 的 updated() 钩子来做到这一点:
php
<?php // resources/views/components/post/⚡edit.blade.php
use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public Post $post;
#[Validate('required')]
public $title = '';
#[Validate('required')]
public $content = '';
public function mount(Post $post)
{
$this->post = $post;
$this->title = $post->title;
$this->content = $post->content;
}
public function updated($name, $value)
{
$this->post->update([
$name => $value,
]);
}
};
?>
<form wire:submit>
<input type="text" wire:model.blur="title">
<div>
@error('title') <span class="error">{{ $message }}</span> @enderror
</div>
<input type="text" wire:model.blur="content">
<div>
@error('content') <span class="error">{{ $message }}</span> @enderror
</div>
</form>在上面的示例中,当用户完成一个字段(通过点击或 tab 到下一个字段)时,会发送网络请求以更新组件上的该属性。在类上更新属性后,会立即为该特定属性名称及其新值调用 updated() 钩子。
我们可以使用这个钩子只更新数据库中的特定字段。
此外,因为我们在这些属性上附加了 #[Validate] 属性,验证规则将在更新属性和调用 updated() 钩子之前运行。
要了解更多关于"updated"生命周期钩子和其他钩子,请访问生命周期钩子文档。
显示脏状态指示器
在上面讨论的实时保存场景中,向用户指示字段尚未持久化到数据库可能很有帮助。
例如,如果用户访问 post.edit 页面并开始在文本输入中修改帖子的标题,他们可能不清楚标题何时实际在数据库中更新,特别是如果表单底部没有"Save"按钮。
Livewire 提供了 wire:dirty 指令,允许你在输入的值与服务器端组件不同时切换元素或修改类:
blade
<input type="text" wire:model.blur="title" wire:dirty.class="border-yellow">在上面的示例中,当用户在输入字段中输入时,字段周围会出现黄色边框。当用户 tab 离开时,网络请求被发送,边框将消失;向他们表示输入已被持久化,不再是"脏"的。
如果你想切换整个元素的可见性,你可以通过将 wire:dirty 与 wire:target 结合使用来做到这一点。wire:target 用于指定你想要观察"脏状态"的数据。在这种情况下,是"title"字段:
blade
<input type="text" wire:model="title">
<div wire:dirty wire:target="title">Unsaved...</div>对输入进行防抖
当在文本输入上使用 .live 时,你可能需要对网络请求发送频率进行更细粒度的控制。默认情况下,对输入应用"250ms"的防抖;但是,你可以使用 .debounce 修饰符自定义此设置:
blade
<input type="text" wire:model.live.debounce.150ms="title" >现在 .debounce.150ms 已添加到字段,处理此字段的输入更新时将使用更短的"150ms"防抖。换句话说,当用户输入时,只有在用户停止输入至少 150 毫秒时才会发送网络请求。
对输入进行节流
如前所述,当对字段应用输入防抖时,在用户停止输入一定时间之前不会发送网络请求。这意味着如果用户继续输入长消息,在用户完成之前不会发送网络请求。
有时这不是期望的行为,你更希望在用户输入时发送请求,而不是在他们完成或休息时。
在这些情况下,你可以使用 .throttle 来指定发送网络请求的时间间隔:
blade
<input type="text" wire:model.live.throttle.150ms="title" >在上面的示例中,当用户在"title"字段中连续输入时,每 150 毫秒会发送一次网络请求,直到用户完成。
将输入字段提取到 Blade 组件
即使在像我们一直讨论的 post.create 这样的小组件中,我们最终也会重复很多表单字段样板代码,如验证消息和标签。
将这些重复的 UI 元素提取到专用的 Blade 组件中以在整个应用程序中共享可能会很有帮助。
例如,下面是 post.create 组件的原始 Blade 模板。我们将把以下两个文本输入提取到专用的 Blade 组件中:
blade
<form wire:submit="save">
<input type="text" wire:model="title">
<div>
@error('title') <span class="error">{{ $message }}</span> @enderror
</div>
<input type="text" wire:model="content">
<div>
@error('content') <span class="error">{{ $message }}</span> @enderror
</div>
<button type="submit">Save</button>
</form>以下是提取一个名为 <x-input-text> 的可重用 Blade 组件后模板的样子:
blade
<form wire:submit="save">
<x-input-text name="title" wire:model="title" />
<x-input-text name="content" wire:model="content" />
<button type="submit">Save</button>
</form>接下来,这是 x-input-text 组件的源代码:
blade
<!-- resources/views/components/input-text.blade.php -->
@props(['name'])
<input type="text" name="{{ $name }}" {{ $attributes }}>
<div>
@error($name) <span class="error">{{ $message }}</span> @enderror
</div>如你所见,我们获取了重复的 HTML 并将其放入一个专用的 Blade 组件中。
在大多数情况下,Blade 组件只包含从原始组件提取的 HTML。但是,我们添加了两件事:
@props指令- 输入上的
语句
让我们讨论每个添加的内容:
通过使用 @props(['name']) 指定 name 作为"prop",我们告诉 Blade:如果在此组件上设置了名为"name"的属性,请获取其值并使其在此组件内部作为 $name 可用。
对于其他没有明确用途的属性,我们使用了 语句。这用于"属性转发",换句话说,获取在 Blade 组件上编写的任何 HTML 属性并将它们转发到组件内的元素上。
这确保 wire:model="title" 和任何其他额外属性如 disabled、class="..." 或 required 仍然被转发到实际的 <input> 元素。
自定义表单控件
在前面的示例中,我们将输入元素"包装"成一个可重用的 Blade 组件,我们可以像使用原生 HTML 输入元素一样使用它。
这种模式非常有用;但是,在某些情况下,你可能想从头开始创建一个完整的输入组件(没有底层原生输入元素),但仍然能够使用 wire:model 将其值绑定到 Livewire 属性。
例如,假设你想创建一个 <x-input-counter /> 组件,这是一个用 Alpine 编写的简单"计数器"输入。
在我们创建 Blade 组件之前,让我们先看一个简单的纯 Alpine"counter"组件作为参考:
blade
<div x-data="{ count: 0 }">
<button x-on:click="count--">-</button>
<span x-text="count"></span>
<button x-on:click="count++">+</button>
</div>如你所见,上面的组件显示一个数字以及两个按钮来增加和减少该数字。
现在,假设我们想将此组件提取到一个名为 <x-input-counter /> 的 Blade 组件中,我们将在组件中这样使用它:
blade
<x-input-counter wire:model="quantity" />创建此组件大部分是简单的。我们获取计数器的 HTML 并将其放入 Blade 组件模板中,如 resources/views/components/input-counter.blade.php。
但是,使其与 wire:model="quantity" 一起工作,以便你可以轻松地将 Livewire 组件中的数据绑定到此 Alpine 组件内部的"count",需要额外一步。
这是组件的源代码:
blade
<!-- resources/view/components/input-counter.blade.php -->
<div x-data="{ count: 0 }" x-modelable="count" {{ $attributes}}>
<button x-on:click="count--">-</button>
<span x-text="count"></span>
<button x-on:click="count++">+</button>
</div>如你所见,这个 HTML 的唯一不同之处是 x-modelable="count" 和 。
x-modelable 是 Alpine 中的一个工具,它告诉 Alpine 使某个数据可以从外部绑定。Alpine 文档有关于此指令的更多信息。
,正如我们之前探讨的,将从外部传递到 Blade 组件的任何属性转发。在这种情况下,wire:model 指令将被转发。
因为有 ,当 HTML 在浏览器中渲染时,wire:model="quantity" 将与 x-modelable="count" 一起渲染在 Alpine 组件的根 <div> 上,如下所示:
blade
<div x-data="{ count: 0 }" x-modelable="count" wire:model="quantity">x-modelable="count" 告诉 Alpine 查找任何 x-model 或 wire:model 语句,并使用"count"作为要绑定到它们的数据。
因为 x-modelable 对 wire:model 和 x-model 都有效,你也可以在 Livewire 和 Alpine 中互换使用此 Blade 组件。以下是在纯 Alpine 上下文中使用此 Blade 组件的示例:
blade
<x-input-counter x-model="quantity" />在应用程序中创建自定义输入元素非常强大,但需要对 Livewire 和 Alpine 提供的工具以及它们如何相互作用有更深入的理解。
另请参阅
- 验证 — 通过实时反馈验证表单输入
- wire:model — 将表单输入绑定到组件属性
- 文件上传 — 在表单中处理文件上传
- 操作 — 使用操作处理表单提交
- 加载状态 — 在表单提交期间显示加载指示器