主题
表单
由于表单是大多数 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 来捕获点击"保存"按钮时的 submit 事件并调用 save() 操作。此操作将将表单输入持久化到数据库。
在数据库中创建新文章后,我们将用户重定向到文章页面并向他们显示一条"闪现"消息,表示已创建新文章。
添加验证
为了避免存储不完整或危险的用户输入,大多数表单需要某种形式的输入验证。
Livewire 使验证表单像在你想要验证的属性上方添加 #[Validate] 属性一样简单。
一旦属性附加了 #[Validate] 属性,验证规则将在其在服务器端更新时应用于属性的值。
让我们在 post.create 组件中的 $title 和 $content 属性上添加一些基本验证规则:
php
<?php // resources/views/components/post/⚡create.blade.php
use Livewire\Attributes\Validate; // [tl! highlight]
use Livewire\Component;
use App\Models\Post;
new class extends Component
{
#[Validate('required')] // [tl! highlight]
public $title = '';
#[Validate('required')] // [tl! highlight]
public $content = '';
public function save()
{
$this->validate(); // [tl! highlight]
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 <!-- [tl! highlight] -->
</div>
<input type="text" wire:model="content">
<div>
@error('content') <span class="error">{{ $message }}</span> @enderror <!-- [tl! highlight] -->
</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; // [tl! highlight]
public function save()
{
$this->validate();
Post::create(
$this->form->only(['title', 'content']) // [tl! highlight]
);
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() // [tl! highlight:5]
{
$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(); // [tl! highlight]
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(); // [tl! highlight]
}
}你还可以通过将属性名称传递给 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() // [tl! highlight]
);
}
}你还可以通过将属性名称传递给 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), // [tl! highlight]
],
'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] // [tl! highlight]
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 在"保存"按钮上添加一个小加载旋转器的示例,以便用户了解表单正在提交:
blade
<button type="submit">
Save
<div wire:loading>
<svg>...</svg> <!-- SVG loading spinner -->
</div>
</button>现在,当用户按下"保存"时,将显示一个小的内联旋转器。
Livewire 的 wire:loading 功能还有更多可提供。访问加载文档以了解更多。
实时更新字段
默认情况下,Livewire 仅在提交表单(或调用任何其他操作)时发送网络请求,而不是在填写表单时。
以 post.create 组件为例。如果你想确保在用户输入时将"标题"输入字段与后端的 $title 属性同步,可以在 wire:model 中添加 .live 修饰符,如下所示:
blade
<input type="text" wire:model.live="title">现在,当用户在此字段中输入时,将发送网络请求到服务器以更新 $title。这对于实时搜索等场景很有用,其中当用户在搜索框中输入时会过滤数据集。
仅在 blur 时更新字段
在大多数情况下,wire:model.live 对于实时表单字段更新是可以的;然而,对于文本输入,它可能过于耗费网络资源。
如果不想在用户输入时发送网络请求,而是只在用户"按 Tab 键"离开文本输入时才发送请求(也称为输入"失焦"),你可以使用 .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 = '';现在,如果用户只在"标题"输入中输入三个字符,然后点击表单中的下一个输入,将向他们显示一条验证消息,指示该字段有五个字符的最小值。
有关更多信息,请查阅验证文档页面。
实时表单保存
如果你想在用户填写表单时自动保存表单,而不是等到用户点击"提交",你可以使用 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) // [tl! highlight:5]
{
$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() 钩子之前运行。
要了解更多关于"更新"生命周期钩子和其他钩子的信息,访问生命周期钩子文档。
显示脏指示器
在上面讨论的实时保存场景中,向用户指示字段尚未持久化到数据库可能很有帮助。
例如,如果用户访问 post.edit 页面并开始在文本输入中修改文章标题,尤其是如果表单底部没有"保存"按钮,他们可能不清楚标题何时实际在数据库中更新。
Livewire 提供了 wire:dirty 指令,允许你在输入的值与服务器端组件不同时切换元素或修改类:
blade
<input type="text" wire:model.blur="title" wire:dirty.class="border-yellow">在上面的示例中,当用户在输入字段中输入时,字段周围将显示黄色边框。当用户按 Tab 键离开时,将发送网络请求并且边框将消失;向他们发信号表示输入已被持久化并且不再"脏"。
如果你想切换整个元素的可见性,可以通过将 wire:dirty 与 wire:target 结合使用来实现。wire:target 用于指定你想要监视"脏"的数据片段。在这种情况下,即"标题"字段:
blade
<input type="text" wire:model="title">
<div wire:dirty wire:target="title">Unsaved...</div>防抖输入
在文本输入上使用 .live 时,你可能想要更精细地控制发送网络请求的频率。默认情况下,输入会应用"250毫秒"的防抖;但是,你可以使用 .debounce 修饰符自定义它:
blade
<input type="text" wire:model.live.debounce.150ms="title" >现在将 .debounce.150ms 添加到字段后,处理此字段的输入更新时将使用更短的"150毫秒"防抖。换句话说,当用户输入时,只有当用户停止输入至少 150 毫秒时才会发送网络请求。
节流输入
如前所述,当将输入防抖应用于字段时,在用户停止输入一定时间之前不会发送网络请求。这意味着如果用户继续输入一条长消息,在用户完成之前不会发送网络请求。
有时这不是所需的行为,你更愿意在用户输入时发送请求,而不是在他们完成或休息时。
在这些情况下,你可以使用 .throttle 来指定发送网络请求的时间间隔:
blade
<input type="text" wire:model.live.throttle.150ms="title" >在上面的示例中,当用户在"标题"字段中持续输入时,将每 150 毫秒发送一次网络请求,直到用户完成。
提取输入字段到 Blade 组件
即使在我们一直讨论的 post.create 示例这样的小组件中,我们最终也会重复大量的表单字段样板代码,如验证消息和标签。
将这些重复的 UI 元素提取到专用的 Blade 组件中以在应用程序中共享可能会很有帮助。
例如,下面是 post.create 组件的原始 Blade 模板。我们将把以下两个文本输入提取到专用的 Blade 组件中:
blade
<form wire:submit="save">
<input type="text" wire:model="title"> <!-- [tl! highlight:3] -->
<div>
@error('title') <span class="error">{{ $message }}</span> @enderror
</div>
<input type="text" wire:model="content"> <!-- [tl! highlight:3] -->
<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" /> <!-- [tl! highlight] -->
<x-input-text name="content" wire:model="content" /> <!-- [tl! highlight] -->
<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 的"计数器"组件作为参考:
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 并将其放置在像 resources/views/components/input-counter.blade.php 这样的 Blade 组件模板内。
然而,要使它与 wire:model="quantity" 一起工作,以便你可以轻松地将数据从 Livewire 组件绑定到此 Alpine 组件内的"计数",需要额外一步。
以下是该组件的源代码:
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 语句,并使用"计数"作为要绑定的数据。
由于 x-modelable 对 wire:model 和 x-model 都有效,你还可以将此 Blade 组件与 Livewire 和 Alpine 交换使用。下面是在纯 Alpine 上下文中使用此 Blade 组件的示例:
blade
<x-input-counter x-model="quantity" />在应用程序中创建自定义输入元素非常强大,但需要更深入地了解 Livewire 和 Alpine 提供的实用工具以及它们如何相互交互。