生命周期挂钩

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

Livewire 提供了各种生命周期挂钩,允许您在组件生命周期的特定点执行代码。这些挂钩使您能够在特定事件之前或之后执行操作,例如初始化组件、更新属性或呈现模板。

以下是所有可用的组件生命周期挂钩列表

挂钩方法 说明
mount() 在创建组件时调用
hydrate() 在后续请求开始时重新水化组件时调用
boot() 在每个请求(包括初始请求和后续请求)开始时调用
updating() 在更新组件属性之前调用
updated() 在更新属性后调用
rendering() 在调用 render() 之前调用
rendered() 在调用 render() 之后调用
dehydrate() 在每个组件请求结束时调用
exception($e, $stopProgation) 在抛出异常时调用

Mount

在标准 PHP 类中,构造函数 (__construct()) 接收外部参数并初始化对象的 state。但是,在 Livewire 中,您使用 mount() 方法来接受参数并初始化组件的 state。

Livewire 组件不使用 __construct(),因为 Livewire 组件在后续网络请求中会重新构建,而我们只希望在首次创建组件时初始化组件一次。

以下是如何使用 mount() 方法初始化 UpdateProfile 组件的 nameemail 属性的示例

use Illuminate\Support\Facades\Auth;
use Livewire\Component;
 
class UpdateProfile extends Component
{
public $name;
 
public $email;
 
public function mount()
{
$this->name = Auth::user()->name;
 
$this->email = Auth::user()->email;
}
 
// ...
}

如前所述,mount() 方法接收作为方法参数传递到组件中的数据

use Livewire\Component;
use App\Models\Post;
 
class UpdatePost extends Component
{
public $title;
 
public $content;
 
public function mount(Post $post)
{
$this->title = $post->title;
 
$this->content = $post->content;
}
 
// ...
}
您可以在所有挂钩方法中使用依赖注入

Livewire 允许您通过在生命周期挂钩上对方法参数进行类型提示,从 Laravel 的服务容器 中解析依赖项。

mount() 方法是使用 Livewire 的关键部分。以下文档提供了使用 mount() 方法完成常见任务的更多示例

启动

mount() 虽然有帮助,但它只在每个组件生命周期中运行一次,您可能希望在给定组件的每次服务器请求开始时运行逻辑。

对于这些情况,Livewire 提供了一个 boot() 方法,您可以在其中编写组件设置代码,以便在每次启动组件类时(在初始化和后续请求中)运行它。

boot() 方法可用于初始化受保护属性等操作,这些属性不会在请求之间持久化。以下是将受保护属性初始化为 Eloquent 模型的示例

use Livewire\Attributes\Locked;
use Livewire\Component;
use App\Models\Post;
 
class ShowPost extends Component
{
#[Locked]
public $postId = 1;
 
protected Post $post;
 
public function boot()
{
$this->post = Post::find($this->postId);
}
 
// ...
}

您可以使用此技术完全控制在 Livewire 组件中初始化组件属性。

大多数情况下,您可以使用计算属性

上面使用的技术很强大;但是,通常最好使用 Livewire 的计算属性 来解决此用例。

始终锁定敏感的公共属性

如您在上面看到的,我们在 $postId 属性上使用了 #[Locked] 属性。在上述情况下,您希望确保 $postId 属性不会被客户端的用户篡改,在使用它之前授权属性的值或向属性添加 #[Locked] 以确保它永远不会被更改非常重要。

有关更多信息,请查看 锁定属性的文档

更新

客户端用户可以通过多种不同的方式更新公共属性,最常见的是修改带有 wire:model 的输入。

Livewire 提供了便捷的挂钩来拦截公共属性的更新,以便您在设置值之前验证或授权值,或确保属性以给定格式设置。

以下是使用 updating 来防止修改 $postId 属性的示例。

值得注意的是,对于这个特定示例,在实际应用程序中,你应该使用 #[Locked] 属性,就像上面的示例中一样。

use Exception;
use Livewire\Component;
 
class ShowPost 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() 来确保属性值保持一致的示例

use Livewire\Component;
 
class CreateUser 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 允许你直接将属性名称指定为方法名称的一部分。以下是上面示例的重写,利用了此技术

use Livewire\Component;
 
class CreateUser extends Component
{
public $username = '';
 
public $email = '';
 
public function updatedUsername()
{
$this->username = strtolower($this->username);
}
 
// ...
}

当然,你也可以将此技术应用于 updating 钩子。

数组

数组属性有一个额外的 $key 参数传递给这些函数,以指定更改的元素。

请注意,当更新数组本身而不是特定键时,$key 参数为 null。

use Livewire\Component;
 
class UpdatePreferences extends Component
{
public $preferences = [];
 
public function updatedPreferences($value, $key)
{
// $value = 'dark'
// $key = 'theme'
}
 
// ...
}

水化和脱水

水化和脱水是鲜为人知且鲜少使用的钩子。但是,在特定场景中,它们可能很强大。

术语“脱水”和“水化”指的是 Livewire 组件被序列化为 JSON 以用于客户端,然后在后续请求中反序列化回 PHP 对象。

我们经常使用术语“水化”和“脱水”来指代 Livewire 代码库和文档中的此过程。如果你想更清楚地了解这些术语,你可以通过 查阅我们的水化文档 来了解更多信息。

让我们看一个示例,它同时使用了 mount()hydrate()dehydrate(),以支持使用自定义 数据传输对象 (DTO) 而不是 Eloquent 模型来存储组件中的帖子数据

use Livewire\Component;
 
class ShowPost 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();
}
 
// ...
}

现在,你可以从组件中的操作和其他位置访问 PostDto 对象,而不是原始数据。

上面的示例主要演示了 hydrate()dehydrate() 钩子的能力和性质。但是,建议您使用 可连接对象或合成器 来完成此操作。

渲染

如果您想连接到渲染组件的 Blade 视图的过程,可以使用 rendering()rendered() 钩子来实现

use Livewire\Component;
use App\Models\Post;
 
class ShowPosts extends Component
{
public function render()
{
return view('livewire.show-posts', [
'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() 钩子允许您执行此操作:您可以对 $error 执行检查,并使用 $stopPropagation 参数来捕获问题。当您想要停止进一步执行代码(提前返回)时,这也解锁了强大的模式,这就是 validate() 等内部方法的工作方式。

use Livewire\Component;
 
class ShowPost extends Component
{
public function mount()
{
$this->post = Post::find($this->postId);
}
 
public function exception($e, $stopPropagation) {
if($e instanceof NotFoundException) {
$this->notify('Post is not found')
$stopPropagation();
}
}
 
// ...
}

在特征中使用钩子

特征是跨组件重用代码或从单个组件中提取代码到专用文件的有用方法。

为避免在声明生命周期钩子方法时多个特征相互冲突,Livewire 支持使用声明它们的当前特征的驼峰式名称为钩子方法添加前缀。

通过这种方式,您可以让多个特征使用相同的生命周期钩子,并避免冲突的方法定义。

下面是一个引用名为 HasPostForm 的特征的组件的示例

use Livewire\Component;
 
class CreatePost extends Component
{
use HasPostForm;
 
// ...
}

现在,以下是包含所有可用前缀钩子的实际 HasPostForm 特征

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()
{
// ...
}
 
// ...
}