表单

您是视觉学习者吗?
使用我们的深入屏幕录制掌握 Livewire
立即观看

由于表单是大多数 Web 应用程序的主干,Livewire 提供了许多有用的实用工具来构建它们。从处理简单的输入元素到实时验证或文件上传等复杂内容,Livewire 拥有简单且有据可查的工具,让您的生活更轻松,让您的用户满意。

让我们深入了解一下。

提交表单

让我们从查看 CreatePost 组件中的一个非常简单的表单开始。此表单将有两个简单的文本输入和一个提交按钮,以及一些后端代码来管理表单的状态和提交

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class CreatePost 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');
}
 
public function render()
{
return view('livewire.create-post');
}
}
<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 来捕获单击“保存”按钮时的 submit 事件并调用 save() 操作。此操作会将表单输入持久保存到数据库。

在数据库中创建新帖子后,我们将用户重定向到 ShowPosts 组件页面,并向他们显示一条“闪现”消息,表示已创建新帖子。

添加验证

为了避免存储不完整或危险的用户输入,大多数表单都需要某种输入验证。

Livewire 使验证表单变得像在您要验证的属性上方添加 #[Validate] 属性一样简单。

一旦属性附加了 #[Validate] 属性,每次在服务器端更新属性值时,验证规则都会应用到该属性值。

让我们向 CreatePost 组件中的 $title$content 属性添加一些基本验证规则

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\Post;
 
class CreatePost 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');
}
 
public function render()
{
return view('livewire.create-post');
}
}

我们还将修改 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 命令

php artisan livewire:form PostForm

以上命令将创建一个名为 app/Livewire/Forms/PostForm.php 的文件。

让我们重写 CreatePost 组件,以使用 PostForm

<?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
 
namespace App\Livewire;
 
use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;
 
class CreatePost extends Component
{
public PostForm $form;
 
public function save()
{
$this->validate();
 
Post::create(
$this->form->all()
);
 
return $this->redirect('/posts');
}
 
public function render()
{
return view('livewire.create-post');
}
}
<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
 
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->all());
}
}

现在你可以从组件中调用 $this->form->store()

class CreatePost extends Component
{
public PostForm $form;
 
public function save()
{
$this->form->store();
 
return $this->redirect('/posts');
}
 
// ...
}

如果你想将此表单对象同时用于创建和更新表单,你可以轻松地调整它来处理这两种用例。

以下是将此相同的表单对象用于 UpdatePost 组件并用初始数据填充它的样子

<?php
 
namespace App\Livewire;
 
use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;
 
class UpdatePost extends Component
{
public PostForm $form;
 
public function mount(Post $post)
{
$this->form->setPost($post);
}
 
public function save()
{
$this->form->update();
 
return $this->redirect('/posts');
}
 
public function render()
{
return view('livewire.create-post');
}
}
<?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->post->update(
$this->all()
);
}
}

如你所见,我们已向 PostForm 对象添加了一个 setPost() 方法,以选择性地允许用现有数据填充表单以及将帖子存储在表单对象中以供以后使用。我们还添加了一个 update() 方法来更新现有帖子。

在使用 Livewire 时,表单对象不是必需的,但它们确实为保持组件没有重复样板代码提供了一个很好的抽象。

重置表单字段

如果你正在使用表单对象,你可能希望在提交表单后重置表单。这可以通过调用 reset() 方法来完成

<?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->all());
 
$this->reset();
}
}

您还可以通过将属性名称传递到 reset() 方法中来重置特定属性

$this->reset('title');
 
// Or multiple at once...
 
$this->reset(['title', 'content']);

提取表单字段

或者,您可以使用 pull() 方法同时检索表单的属性并在一个操作中重置它们。

<?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() 方法中来提取特定属性

// Return a value before resetting...
$this->pull('title');
 
// Return a key-value array of properties before resetting...
$this->pull(['title', 'content']);

使用规则对象

如果您有更复杂的验证场景,其中需要 Laravel 的 Rule 对象,则可以选择定义一个 rules() 方法来声明您的验证规则,如下所示

<?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 = '';
 
public function rules()
{
return [
'title' => [
'required',
Rule::unique('posts')->ignore($this->post),
],
'content' => 'required|min:5',
];
}
 
// ...
 
public function update()
{
$this->validate();
 
$this->post->update($this->all());
 
$this->reset();
}
}

当使用 rules() 方法而不是 #[Validate] 时,Livewire 仅在您调用 $this->validate() 时运行验证规则,而不是在每次更新属性时运行。

如果您正在使用实时验证或任何其他场景,您希望 Livewire 在每次请求后验证特定字段,则可以使用 #[Validate] 而无需提供任何规则,如下所示

<?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 = '';
 
public function rules()
{
return [
'title' => [
'required',
Rule::unique('posts')->ignore($this->post),
],
'content' => 'required|min:5',
];
}
 
// ...
 
public function update()
{
$this->validate();
 
$this->post->update($this->all());
 
$this->reset();
}
}

现在,如果在提交表单之前更新了 $title 属性(例如在使用 wire:model.blur 时),将运行 $title 的验证。

显示加载指示器

默认情况下,在提交表单时,Livewire 将自动禁用提交按钮并将输入标记为 readonly,从而防止用户在处理第一次提交时再次提交表单。

但是,如果没有应用程序 UI 中的额外功能,用户可能很难检测到此“加载”状态。

以下是如何通过 wire:loading 向“保存”按钮添加一个小加载旋转器,以便用户了解表单正在提交

<button type="submit">
Save
 
<div wire:loading>
<svg>...</svg> <!-- SVG loading spinner -->
</div>
</button>

现在,当用户按下“保存”时,将显示一个小型的内联旋转器。

Livewire 的 wire:loading 功能还有更多功能。访问 加载文档以了解更多信息。

实时更新字段

默认情况下,Livewire 仅在表单提交(或调用任何其他 操作)时发送网络请求,而不是在填写表单时发送。

CreatePost 组件为例。如果您希望确保“标题”输入字段在用户键入时与后端的 $title 属性同步,则可以将 .live 修饰符添加到 wire:model,如下所示

<input type="text" wire:model.live="title">

现在,当用户在此字段中键入时,将向服务器发送网络请求以更新 $title。这对于实时搜索等内容非常有用,其中数据集在用户在搜索框中键入时进行过滤。

仅在失去焦点时更新字段

对于大多数情况,wire:model.live 对于实时表单字段更新来说很好;但是,它在文本输入上可能过于消耗网络资源。

如果您希望仅在用户“制表”退出文本输入(也称为“使输入失焦”)时发送网络请求,而不是在用户键入时发送网络请求,则可以使用 .blur 修饰符

<input type="text" wire:model.blur="title" >

现在,服务器上的组件类不会更新,直到用户按 Tab 键或单击文本输入之外的位置。

实时验证

有时,您可能希望在用户填写表单时显示验证错误。这样,他们会及早收到警报,表明出了问题,而不是等到填写完整个表单。

Livewire 会自动处理这类事情。通过对 wire:model 使用 .live.blur,Livewire 会在用户填写表单时发送网络请求。在更新每个属性之前,每个网络请求都会运行适当的验证规则。如果验证失败,则该属性不会在服务器上更新,并且会向用户显示验证消息

<input type="text" wire:model.blur="title">
 
<div>
@error('title') <span class="error">{{ $message }}</span> @enderror
</div>
#[Validate('required|min:5')]
public $title = '';

现在,如果用户仅在“标题”输入中键入三个字符,然后单击表单中的下一个输入,则会向他们显示一条验证消息,表明该字段的最小长度为五个字符。

有关更多信息,请查看 验证文档页面

实时表单保存

如果你想在用户填写表单时自动保存表单,而不是等到用户点击“提交”,你可以使用 Livewire 的 updated() 钩子

<?php
 
namespace App\Livewire;
 
use Livewire\Attributes\Validate;
use Livewire\Component;
use App\Models\Post;
 
class UpdatePost 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,
]);
}
 
public function render()
{
return view('livewire.create-post');
}
}
<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>

在上面的示例中,当用户完成一个字段(通过单击或制表键切换到下一个字段)时,会发送一个网络请求来更新组件上的该属性。在类上更新属性后,会针对该特定属性名称及其新值调用 updated() 钩子。

我们可以使用此钩子仅更新数据库中的该特定字段。

此外,由于我们已将 #[Validate] 属性附加到这些属性,因此在更新属性和调用 updated() 钩子之前将运行验证规则。

要了解有关“updated”生命周期钩子和其他钩子的更多信息,请访问生命周期钩子文档

显示脏指示符

在上面讨论的实时保存场景中,指出用户何时尚未将字段持久保存到数据库中可能会有所帮助。

例如,如果用户访问 UpdatePost 页面并开始在文本输入中修改帖子的标题,则他们可能不清楚标题何时实际在数据库中更新,尤其是在表单底部没有“保存”按钮的情况下。

Livewire 提供了 wire:dirty 指令,允许你在输入值与服务器端组件不同时切换元素或修改类

<input type="text" wire:model.blur="title" wire:dirty.class="border-yellow">

在上面的示例中,当用户在输入字段中键入时,字段周围会出现一个黄色边框。当用户切换选项卡时,将发送网络请求,边框将消失;向他们发出信号,表明输入已持久保存且不再“脏”。

如果您想切换整个元素的可见性,可以使用 wire:dirty 结合 wire:target 来实现。wire:target 用于指定您想要监视其“脏”状态的数据部分。在本例中,为“title”字段

<input type="text" wire:model="title">
 
<div wire:dirty wire:target="title">Unsaved...</div>

防抖输入

在文本输入上使用 .live 时,您可能希望更精细地控制发送网络请求的频率。默认情况下,对输入应用“250 毫秒”的防抖;但是,您可以使用 .debounce 修饰符自定义此设置

<input type="text" wire:model.live.debounce.150ms="title" >

现在已将 .debounce.150ms 添加到字段,在处理此字段的输入更新时将使用更短的“150 毫秒”防抖。换句话说,当用户键入时,只有当用户停止键入至少 150 毫秒时才会发送网络请求。

节流输入

如前所述,当对字段应用输入防抖时,在用户停止键入一段时间后才会发送网络请求。这意味着如果用户继续键入一条长消息,则在用户完成之前不会发送网络请求。

有时这不是期望的行为,您希望在用户键入时发送请求,而不是在他们完成或休息时发送请求。

在这些情况下,您可以改用 .throttle 来表示发送网络请求的时间间隔

<input type="text" wire:model.live.throttle.150ms="title" >

在上面的示例中,当用户在“title”字段中连续键入时,每 150 毫秒就会发送一个网络请求,直到用户完成。

将输入字段提取到 Blade 组件

即使在像我们一直在讨论的 CreatePost 示例这样的小组件中,我们最终也会重复很多表单字段样板,例如验证消息和标签。

将重复的 UI 元素(如这些元素)提取到专用的 Blade 组件 中,以便在整个应用程序中共享,这可能会有所帮助。

例如,以下是 CreatePost 组件中的原始 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 组件后,模板将如下所示

<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 组件的源代码

<!-- 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 指令
  • 输入上的 {{ $attributes }} 语句

让我们讨论一下这些新增内容

通过使用 @props(['name'])name 指定为“prop”,我们告诉 Blade:如果此组件上设置了名为“name”的属性,请获取其值,并将其作为 $name 在此组件中提供。

对于没有明确用途的其他属性,我们使用了 {{ $attributes }} 语句。这用于“属性转发”,换句话说,获取在 Blade 组件上编写的任何 HTML 属性,并将其转发到组件内的元素上。

这确保了 wire:model="title" 和任何其他额外属性(如 disabledclass="..."required)仍会转发到实际的 <input> 元素。

自定义表单控件

在前面的示例中,我们将一个输入元素“包装”到一个可重用的 Blade 组件中,我们可以像使用本机 HTML 输入元素一样使用它。

此模式非常有用;但是,在某些情况下,你可能希望从头开始创建一个完整的输入组件(没有底层的本机输入元素),但仍可以使用 wire:model 将其值绑定到 Livewire 属性。

例如,假设你想创建一个 <x-input-counter /> 组件,这是一个用 Alpine 编写的简单“计数器”输入。

在我们创建 Blade 组件之前,让我们先看一个简单的纯 Alpine “计数器”组件作为参考

<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 组件中,我们将在组件中像这样使用它

<x-input-counter wire:model="quantity" />

创建此组件非常简单。我们将计数器的 HTML 提取出来,并将其放在 Blade 组件模板中,例如 resources/views/components/input-counter.blade.php

但是,要使其与 wire:model="quantity" 一起使用,以便你可以轻松地将数据从 Livewire 组件绑定到此 Alpine 组件内的“计数”,需要一个额外的步骤。

以下是该组件的源代码

<!-- 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"{{ $attributes }}

x-modelable 是 Alpine 中的一个实用程序,它告诉 Alpine 使某些数据可用于外部绑定。Alpine 文档对此指令有更详细的说明。

{{ $attributes }},正如我们之前探讨的那样,它会将从外部传递到 Blade 组件的任何属性转发出去。在这种情况下,会转发 wire:model 指令。

由于 {{ $attributes }},当 HTML 在浏览器中呈现时,wire:model="quantity" 将与 x-modelable="count" 一起呈现在 Alpine 组件的根 <div> 中,如下所示

<div x-data="{ count: 0 }" x-modelable="count" wire:model="quantity">

x-modelable="count" 告诉 Alpine 查找任何 x-modelwire:model 语句,并使用“count”作为要绑定的数据。

由于 x-modelable 同时适用于 wire:modelx-model,因此你还可以将此 Blade 组件与 Livewire 和 Alpine 交替使用。例如,以下是如何在纯 Alpine 上下文中使用此 Blade 组件的示例

<x-input-counter x-model="quantity" />

在应用程序中创建自定义输入元素非常强大,但需要更深入地了解 Livewire 和 Alpine 提供的实用程序以及它们如何相互交互。