生命周期钩子
Livewire 提供多种生命周期钩子,让你在组件生命周期的特定时刻执行代码。借助这些钩子,你可以在初始化组件、更新属性或渲染模板等事件之前或之后执行操作。
以下是所有可用的组件生命周期钩子:
| 钩子方法 | 说明 |
|---|---|
mount() | 组件创建时调用 |
hydrate() | 在后续请求开始时,组件被重新 hydrate 时调用 |
boot() | 每次请求开始时调用(包括首次请求与后续请求) |
updating() | 更新组件属性之前调用 |
updated() | 更新属性之后调用 |
rendering() | 组件视图渲染之前调用 |
rendered() | 组件视图渲染之后调用 |
dehydrate() | 每次组件请求结束时调用 |
exception($e, $stopPropagation) | 抛出异常时调用 |
Mount
在标准 PHP 类中,构造函数(__construct())接收外部参数并初始化对象状态。而在 Livewire 中,你使用 mount() 方法来接收参数并初始化组件状态。
Livewire 组件不使用 __construct(),因为组件会在后续网络请求中被重新构造,而我们只希望在组件首次创建时初始化一次。
下面的示例使用 mount() 方法初始化 profile.edit 组件的 name 和 email 属性:
<?php // resources/views/components/profile/⚡edit.blade.php
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
new class extends Component {
public $name;
public $email;
public function mount()
{
$this->name = Auth::user()->name;
$this->email = Auth::user()->email;
}
// ...
};如前所述,mount() 方法会把传入组件的数据作为方法参数接收:
<?php // resources/views/components/post/⚡edit.blade.php
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public $title;
public $content;
public function mount(Post $post)
{
$this->title = $post->title;
$this->content = $post->content;
}
// ...
};mount() 是使用 Livewire 的关键一环。以下文档提供了更多用 mount() 完成常见任务的示例:
Boot
尽管 mount() 很有用,但它在每个组件生命周期中只运行一次;你可能希望在某个组件每次向服务器发出请求时,都在开头执行一些逻辑。
针对这些场景,Livewire 提供了 boot() 方法,你可以在其中编写希望每次组件类启动时都运行的初始化代码:无论是首次初始化还是后续请求。
boot() 适用于初始化不会在请求之间持久化的 protected 属性等情况。下面是将 protected 属性初始化为 Eloquent 模型的示例:
<?php // resources/views/components/post/⚡show.blade.php
use Livewire\Attributes\Locked;
use Livewire\Component;
use App\Models\Post;
new class extends Component {
#[Locked]
public $postId = 1;
protected Post $post;
public function boot() // [tl! highlight:3]
{
$this->post = Post::find($this->postId);
}
// ...
};你可以用这种技巧,完全掌控 Livewire 组件中属性的初始化过程。
WARNING
务必锁定敏感的公共属性
如上所示,我们在 $postId 属性上使用了 #[Locked] 属性。在这类场景中,若要确保 $postId 不会被客户端用户篡改,应在使用前对该属性值做授权校验,或为属性加上 #[Locked],确保它永远不会被更改。
更多信息,请参阅 Locked 属性文档。
Update
客户端用户可以通过多种方式更新公共属性,最常见的是修改带有 wire:model 的输入框。
Livewire 提供了便捷的钩子,用于拦截公共属性的更新,以便你在赋值前校验或授权,或确保属性以指定格式设置。
下面的示例使用 updating 阻止修改 $postId 属性。
值得注意的是:就这个具体例子而言,在实际应用中你应改用 #[Locked] 属性,就像上面的示例那样。
<?php // resources/views/components/post/⚡show.blade.php
use Exception;
use Livewire\Component;
new class extends Component {
public $postId = 1;
public function updating($property, $value)
{
// $property: The name of the current property being updated
// $value: The value about to be set to the property
if ($property === 'postId') {
throw new Exception;
}
}
// ...
};上面的 updating() 方法在属性更新之前运行,让你可以捕获无效输入并阻止属性更新。下面的示例使用 updated() 确保属性值保持一致:
<?php // resources/views/components/user/⚡create.blade.php
use Livewire\Component;
new class extends Component {
public $username = '';
public $email = '';
public function updated($property)
{
// $property: The name of the current property that was updated
if ($property === 'username') {
$this->username = strtolower($this->username);
}
}
// ...
};这样,每当 $username 在客户端被更新时,我们都会确保该值始终为小写。
使用更新钩子时,你常常只针对某个特定属性,因此 Livewire 允许你把属性名直接写进方法名。下面是上面同一示例用这种写法重写后的版本:
<?php // resources/views/components/user/⚡create.blade.php
use Livewire\Component;
new class extends Component {
public $username = '';
public $email = '';
public function updatedUsername()
{
$this->username = strtolower($this->username);
}
// ...
};当然,你也可以把同样的技巧用在 updating 钩子上。
数组
数组属性会向这些函数额外传入一个 $key 参数,用于指明正在变化的元素。
注意:当更新的是整个数组而非某个特定键时,$key 参数为 null。
<?php // resources/views/components/preferences/⚡edit.blade.php
use Livewire\Component;
new class extends Component {
public $preferences = [];
public function updatedPreferences($value, $key)
{
// $value = 'dark'
// $key = 'theme'
}
// ...
};Hydrate 与 Dehydrate
Hydrate 与 dehydrate 是较少为人知、也较少使用的钩子。不过在某些特定场景下,它们会非常有用。
「dehydrate」和「hydrate」指的是:Livewire 组件被序列化为面向客户端的 JSON,再在后续请求中反序列化回 PHP 对象。
在 Livewire 的代码库和文档中,我们经常用「hydrate」和「dehydrate」来指代这一过程。若想更清楚地理解这些术语,可以参阅我们的 hydration 文档。
来看一个同时使用 mount()、hydrate() 和 dehydrate() 的示例:用自定义 数据传输对象(DTO) 代替 Eloquent 模型,在组件中存储文章数据:
<?php // resources/views/components/post/⚡show.blade.php
use Livewire\Component;
new class extends Component {
public $post;
public function mount($title, $content)
{
// Runs at the beginning of the first initial request...
$this->post = new PostDto([
'title' => $title,
'content' => $content,
]);
}
public function hydrate()
{
// Runs at the beginning of every "subsequent" request...
// This doesn't run on the initial request ("mount" does)...
$this->post = new PostDto($this->post);
}
public function dehydrate()
{
// Runs at the end of every single request...
$this->post = $this->post->toArray();
}
// ...
};现在,你可以在组件内的 action 及其他位置访问 PostDto 对象,而不是原始数据。
上面的示例主要是为了演示 hydrate() 和 dehydrate() 钩子的能力与特性。不过,推荐改用 Wireables 或 Synthesizers 来实现同样的目标。
Render
若要挂钩到组件 Blade 视图的渲染过程,可以使用 rendering() 和 rendered() 钩子:
<?php // resources/views/components/post/⚡index.blade.php
use Livewire\Component;
use App\Models\Post;
new class extends Component {
public function render()
{
return $this->view([
'post' => Post::all(),
]);
}
public function rendering($view, $data)
{
// Runs BEFORE the provided view is rendered...
//
// $view: The view about to be rendered
// $data: The data provided to the view
}
public function rendered($view, $html)
{
// Runs AFTER the provided view is rendered...
//
// $view: The rendered view
// $html: The final, rendered HTML
}
// ...
};Exception
有时拦截并捕获错误会很有用,例如自定义错误消息或忽略特定类型的异常。exception() 钩子正是为此而设:你可以检查 $error,并使用 $stopPropagation 参数来捕获该问题。
当你希望提前停止后续代码执行(提前返回)时,这也解锁了强大的模式——内部方法如 validate() 就是这样工作的。
<?php // resources/views/components/post/⚡show.blade.php
use Livewire\Component;
new class extends Component {
public function mount() // [tl! highlight:3]
{
$this->post = Post::find($this->postId);
}
public function exception($e, $stopPropagation) {
if ($e instanceof NotFoundException) {
$this->notify('Post is not found');
$stopPropagation();
}
}
// ...
};在 trait 中使用钩子
Trait 有助于在多个组件间复用代码,或把单个组件中的代码抽离到独立文件。
为避免多个 trait 在声明生命周期钩子方法时互相冲突,Livewire 支持用当前声明这些方法的 trait 的 camelCase 名称作为钩子方法的前缀。
这样,多个 trait 可以使用相同的生命周期钩子,而不会出现方法定义冲突。
下面是一个引用名为 HasPostForm 的 trait 的组件示例:
<?php // resources/views/components/post/⚡create.blade.php
use Livewire\Component;
new class extends Component {
use HasPostForm;
// ...
};下面是实际的 HasPostForm trait,包含所有可用的带前缀钩子:
trait HasPostForm
{
public $title = '';
public $content = '';
public function mountHasPostForm()
{
// ...
}
public function hydrateHasPostForm()
{
// ...
}
public function bootHasPostForm()
{
// ...
}
public function updatingHasPostForm()
{
// ...
}
public function updatedHasPostForm()
{
// ...
}
public function renderingHasPostForm()
{
// ...
}
public function renderedHasPostForm()
{
// ...
}
public function dehydrateHasPostForm()
{
// ...
}
// ...
}在表单对象中使用钩子
Livewire 中的表单对象支持属性更新钩子。这些钩子的工作方式与组件更新钩子类似,可在表单对象中的属性发生变化时执行操作。
下面是一个使用 PostForm 表单对象的组件示例:
<?php // resources/views/components/post/⚡create.blade.php
use Livewire\Component;
new class extends Component {
public PostForm $form;
// ...
};下面是包含所有可用钩子的 PostForm 表单对象:
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class PostForm extends Form
{
public $title = '';
public $tags = [];
public function updating($property, $value)
{
// ...
}
public function updated($property, $value)
{
// ...
}
public function updatingTitle($value)
{
// ...
}
public function updatedTitle($value)
{
// ...
}
public function updatingTags($value, $key)
{
// ...
}
public function updatedTags($value, $key)
{
// ...
}
// ...
}