Skip to content

生命周期钩子

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

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

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

Mount

在标准的 PHP 类中,构造函数 (__construct()) 接收外部参数并初始化对象的状态。然而,在 Livewire 中,你使用 mount() 方法来接收参数并初始化组件的状态。

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

以下是使用 mount() 方法初始化 profile.edit 组件的 nameemail 属性的示例:

php
<?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
<?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;
    }

    // ...
};

你可以在所有钩子方法中使用依赖注入

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

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

Boot

尽管 mount() 很有用,但它每个组件生命周期只运行一次,而你可能希望在给定组件的每个向服务器的请求开始时运行逻辑。

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

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

php
<?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 组件中组件属性的初始化。

大多数情况下,你可以使用计算属性替代

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

始终锁定敏感的公共属性

如你上面所见,我们在 $postId 属性上使用了 #[Locked] 属性。在上面这种情况下,你想确保 $postId 属性不会被客户端的用户篡改,在使用它之前授权属性的值或在属性上添加 #[Locked] 以确保它永远不会被更改是很重要的。

有关更多信息,请查阅 Locked 属性文档

Update

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

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

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

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

php
<?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: 正在更新的当前属性的名称
        // $value: 即将设置到属性的值

        if ($property === 'postId') {
            throw new Exception;
        }
    }

    // ...
};

上面的 updating() 方法在属性更新之前运行,允许你捕获无效输入并防止属性更新。下面是使用 updated() 确保属性值保持一致的示例:

php
<?php // resources/views/components/user/⚡create.blade.php

use Livewire\Component;

new class extends Component
{
    public $username = '';

    public $email = '';

    public function updated($property)
    {
        // $property: 被更新的当前属性的名称

        if ($property === 'username') {
            $this->username = strtolower($this->username);
        }
    }

    // ...
};

现在,任何时候客户端更新 $username 属性,我们都将确保该值始终为小写。

因为在使用更新钩子时通常针对特定属性,Livewire 允许你直接将属性名称指定为方法名称的一部分。以下是上面相同的示例,但使用此技术重写:

php
<?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
<?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"来指代这个过程。如果你想对这些术语有更清晰的了解,可以通过查阅我们的水合文档了解更多。

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

php
<?php // resources/views/components/post/⚡show.blade.php

use Livewire\Component;

new class extends Component
{
    public $post;

    public function mount($title, $content)
    {
        // 在第一次初始请求开始时运行...

        $this->post = new PostDto([
            'title' => $title,
            'content' => $content,
        ]);
    }

    public function hydrate()
    {
        // 在每个"后续"请求开始时运行...
        // 这不会在初始请求时运行("mount"会)...

        $this->post = new PostDto($this->post);
    }

    public function dehydrate()
    {
        // 在每个请求结束时运行...

        $this->post = $this->post->toArray();
    }

    // ...
};

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

上面的示例主要展示了 hydrate()dehydrate() 钩子的能力和性质。然而,建议你使用 Wireables 或 Synthesizers来完成此操作。

Render

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

php
<?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)
    {
        // 在渲染提供的视图之前运行...
        //
        // $view: 即将被渲染的视图
        // $data: 提供给视图的数据
    }

    public function rendered($view, $html)
    {
        // 在渲染提供的视图之后运行...
        //
        // $view: 渲染的视图
        // $html: 最终渲染的 HTML
    }

    // ...
};

Exception

有时拦截和捕获错误可能很有帮助,例如:自定义错误消息或忽略特定类型的异常。exception() 钩子允许你做到这一点:你可以对 $error 执行检查,并使用 $stopPropagation 参数来捕获问题。 当你想停止进一步执行代码(提前返回)时,这也解锁了强大的模式,这就是像 validate() 这样的内部方法的工作原理。

php
<?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('找不到文章');
            $stopPropagation();
        }
    }

    // ...
};

在 Trait 中使用钩子

Trait 是在组件之间重用代码或将代码从单个组件提取到专用文件的有用方法。

为了避免在声明生命周期钩子方法时多个 trait 相互冲突,Livewire 支持在钩子方法前加上当前 trait 的_驼峰式_名称。

这样,你可以有多个 trait 使用相同的生命周期钩子并避免冲突的方法定义。

下面是一个组件引用名为 HasPostForm 的 trait 的示例:

php
<?php // resources/views/components/post/⚡create.blade.php

use Livewire\Component;

new class extends Component
{
    use HasPostForm;

    // ...
};

现在这里是实际的 HasPostForm trait,包含所有可用的前缀钩子:

php
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
<?php // resources/views/components/post/⚡create.blade.php

use Livewire\Component;

new class extends Component
{
    public PostForm $form;

    // ...
};

以下是包含所有可用钩子的 PostForm 表单对象:

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

    // ...
}