Skip to content

事件

Livewire 提供了一个强大的事件系统,你可以使用它在同一页面上不同组件之间进行通信。

因为它使用浏览器事件,你还可以使用 Livewire 的事件系统与 Alpine 组件甚至普通的原生 JavaScript 通信。

分发事件

要从 Livewire 组件分发事件,你可以从组件类内部调用 dispatch() 方法,传递事件名称和任何你希望与事件一起发送的额外数据。

下面是一个分发"post-created"事件的 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 = Post::create([
            'title' => $this->title,
            'content' => $this->content,
        ]);

        $this->dispatch('post-created');
    }
};

在此示例中,当 dispatch() 方法被调用时,post-created 事件将被分发。

如果你希望使用静态分析工具进行更严格的类型检查,可以使用 Event 枚举代替字符串:

php
use App\Enums\Event;

$this->dispatch(Event::PostCreated);

关于事件需要理解的重要一点是事件被分发的_时机_。如果在 Livewire 组件上运行操作的请求期间调用 dispatch(),实际事件不会被分发,直到该操作的响应被发送到浏览器并由 Livewire 处理。

这是因为 dispatch() 使用浏览器的原生事件系统在后台工作。

监听事件

要监听页面上任何地方分发的事件,你可以在组件类中的方法上方添加 #[On] 属性。

下面是一个名为 Dashboard 的示例组件,它监听 post-created 事件以显示通知横幅:

php
<?php // resources/views/components/⚡dashboard.blade.php

use Livewire\Attributes\On;
use Livewire\Component;

new class extends Component {
    #[On('post-created')]
    public function notifyAboutNewPost()
    {
        // ...
    }
};

同样,你可以使用枚举代替字符串事件名称:

php
use Livewire\Attributes\On;
use App\Enums\Event;

#[On(Event::PostCreated)]
public function notifyAboutNewPost()
{
    // ...
}

现在,当 post-created 事件从任何其他组件分发时,即使是 post.create 组件本身,也会触发网络请求在 Dashboard 上调用 notifyAboutNewPost() 方法,然后重新渲染组件。

传递参数

你还可以通过将参数传递给 dispatch() 方法来发送额外数据:

php
$this->dispatch('post-created', title: $post->title);

事件参数是命名的

操作参数不同,事件参数是命名参数。这意味着你可以从处理程序方法访问它们,只要方法参数名称与传递的参数名称匹配:

php
#[On('post-created')]
public function notifyAboutNewPost($title)
{
    // ...
}

动态事件名称

偶尔,在运行时动态生成事件名称可能很有用。例如,你可能想只监听已登录用户的通知。你可以使用 {paramName} 语法来完成此操作:

php
#[On('notifyUser.{userId}')]
public function notify($title)
{
    // ...
}

public function mount()
{
    $this->userId = Auth::id();
}

当使用此功能时,组件将只响应与给定参数匹配的事件。

从子组件监听

事件对于组件之间的通信很有价值,包括嵌套组件

以下是一个简单的 post.index 父组件的示例,其中显示帖子列表,其中一个帖子嵌套在 post.show 子组件内:

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

use Livewire\Attributes\On;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[On('post-deleted')]
    public function refresh() {}
};
?>

<div>
    @foreach (Post::all() as $post)
        <livewire:post.show :$post :wire:key="$post->id">
    @endforeach
</div>

在下面 post.show 组件的示例中,当用户点击"Delete"时,会删除帖子并从子组件分发 post-deleted 事件。因为父组件(post.index)正在使用 #[On('post-deleted')] 监听该事件,所以它将在帖子被删除后被刷新:

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

use Livewire\Component;
use App\Models\Post;

new class extends Component {
    public Post $post;

    public function delete()
    {
        $this->post->delete();

        $this->dispatch('post-deleted');
    }
};
?>

<div>
    <h1>{{ $post->title }}</h1>
    <p>{{ $post->content }}</p>

    <button wire:click="delete">Delete</button>
</div>

改进性能

上面的示例需要两个网络请求来完成删除帖子和刷新父列表:

  1. 当子组件的 delete 操作被调用时第一个请求
  2. 父组件监听 post-deleted 事件并刷新列表的后续请求

要将这两个网络请求合并为一个,你可以使用 $dispatch() 魔术方法直接从 Blade 模板分发事件。通过将事件分发移动到 Blade,我们可以直接在父组件上触发完整的删除和刷新序列:

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

use Livewire\Attributes\On;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[On('delete-post')]
    public function deletePost($id)
    {
        $post = Post::find($id);

        $this->authorize('delete', $post);

        $post->delete();
    }
};
?>

<div>
    @foreach (Post::all() as $post)
        <div wire:key="{{ $post->id }}">
            <h1>{{ $post->title }}</h1>
            <p>{{ $post->content }}</p>

            <button wire:click="$dispatch('delete-post', { id: {{ $post->id }} })">
                Delete
            </button>
        </div>
    @endforeach
</div>

现在当用户点击"Delete"按钮时,事件被分发并由同一个组件在单个请求中处理。

始终对操作进行授权

当使用事件作为组件通信机制时,与任何公共方法一样重要的是授权操作是否可以执行。恶意用户可能会分发带有意外参数的事件,你必须始终验证操作是否被允许。

从 JavaScript 监听

Livewire 事件本质上是原生浏览器事件,使用标准 JavaScript API 可以使用它们。这允许你直接从页面上的任何 JavaScript 代码监听 Livewire 事件。

全局 JavaScript 监听器

默认情况下,Livewire 事件冒泡到 window 对象,它们可以被应用程序中任何地方的 JavaScript 拦截。要从 Livewire 组件外部的原生 JavaScript 监听事件,可以使用 Livewire.on() 方法:

js
Livewire.on('post-created', (event) => {
    //
})

返回值是一个"清理"函数。你可以调用此函数来注销监听器并清理内存:

js
let cleanup = Livewire.on('post-created', (event) => {
    //
})

cleanup()

如果你通过操作传递额外参数,可以在 event 对象上访问它们:

js
Livewire.on('post-created', (event) => {
    let title = event.title
    let author = event.author
})

Alpine 事件

因为 Livewire 事件作为浏览器事件分发,你可以使用 Alpine 的 @x-on 语法来监听它们。

以下是仅使用 Alpine 在分发事件时关闭模态框的示例:

blade
<div x-data="{ open: true }" @post-created.window="open = false">
    ...
</div>

上面的示例在用户创建新帖子并分发"post-created"事件时关闭模态框。

需要记住两件重要的事情,以了解事件将如何被接收:

  1. 由于事件冒泡到 window,在 Alpine 监听器中需要 .window 修饰符才能正确捕获它
  2. 如果事件传递了参数,Alpine 在其事件对象中通过 $event.detail 接收它们

下面是一个完整的示例,演示了这两个概念:

php
$this->dispatch('post-created', title: 'My First Post');
blade
<div x-data @post-created.window="console.log('Title: ' + $event.detail.title)">
</div>

分发到特定组件

有时你只想将事件分发到特定组件而不是所有组件,比如相同类型的不同实例。

通过使用 self 修饰符,你可以只向当前组件实例分发事件,而不是在页面上监听该事件的其他组件:

php
<?php // resources/views/components/⚡counter.blade.php

use Livewire\Attributes\On;
use Livewire\Component;

new class extends Component {
    public int $count = 0;

    #[On('increment')]
    public function increment()
    {
        $this->count++;
    }
};
?>

<div>
    <span>{{ $count }}</span>

    <button wire:click="$dispatchSelf('increment')">Increment</button>
</div>

在这种情况下,如果页面上有多个 counter 组件,点击"Increment"按钮只会增加你点击的那个特定计数器。

直接分发到另一个组件

或者,你可以使用 $dispatchTo() 将事件直接分发到另一个组件:

blade
<button wire:click="$dispatchTo('post.index', 'post-deleted', { id: {{ $post->id }} })">
    Delete
</button>

第一个参数必须是组件的名称。记住这是一个实时运行的 JavaScript 函数,所以组件的名称是一个字符串,而不是类名。

上面的代码片段将分发 post-deleted 事件到名为 post.index 的组件,传递 id 作为参数。

当你想向特定组件类型的所有实例分发事件时,这很有用。

分发给自己

有时让组件分发事件给自己很有用。这可用于延迟操作或序列化来自同一组件内不同源的事件。

为此,你可以使用 dispatchSelf() 方法而不是 dispatch()

php
$this->dispatchSelf('post-created');

唯一的区别是分发事件时不会冒泡到其他组件。它将只分发给当前组件实例。

从 Blade 模板分发

你可以直接从 Blade 模板使用 $dispatch() JavaScript 函数分发事件。这对于从用户交互如按钮点击分发事件很有用:

blade
<button wire:click="$dispatch('show-post-modal', { id: {{ $post->id }} })">
    Edit Post
</button>

在此示例中,当点击按钮时,show-post-modal 事件将与帖子的 ID 作为参数一起分发。

避免传递对象

因为事件参数使用 JavaScript JSON.stringify() 序列化,传递像 Eloquent 模型这样的对象可能不按预期工作。只传递简单类型如字符串和整数,并使用 ID 在监听组件中查找对象。

测试分发的事件

要测试事件是否从组件分发,你可以在测试中使用 assertDispatched() 方法。

以下是我们的 post.create 组件的测试示例,断言 post-created 事件被分发:

php
it('dispatches a post-created event when a post is created', function() {
    Livewire::test('post.create')
        ->set('title', 'Hello World!')
        ->set('content', 'Lorem ipsum...')
        ->call('save')
        ->assertDispatched('post-created');
});

在上面的示例中,我们创建了一个新帖子,然后断言 post-created 事件被分发。

你还可以断言组件监听事件。以下是我们的 Dashboard 组件的测试示例,断言它监听 post-created 事件并调用 notifyAboutNewPost() 方法。

php
it('listens for the post-created event', function() {
    Livewire::test('dashboard')
        ->dispatch('post-created')
        ->assertCalled('notifyAboutNewPost');
});

测试事件参数

你可以使用 assertDispatched() 的第二个参数测试事件参数:

php
it('dispatches a post-created event with the post title', function() {
    Livewire::test('post.create')
        ->set('title', 'Hello World!')
        ->call('save')
        ->assertDispatched('post-created', title: 'Hello World!');
});

或者,你可以传递一个闭包进行更复杂的断言:

php
->assertDispatched('post-created', function($event, $params) {
    return $params['title'] === 'Hello World!';
});

实时事件

Livewire 与 Laravel Echo 集成,允许你实时监听广播事件。如果你使用 Echo 的 Blade 指令,如 @jsEcho.private(...),你可以使用 Livewire 的 #[On('echo:...'))] 语法直接在 Livewire 组件中监听这些相同的事件。

安装

在进一步之前,必须在你的应用程序中安装和配置 Laravel Echo

监听广播事件

假设你有一个从 Laravel 广播名为 OrderShipped 的事件。通常,你需要在 JavaScript 中设置 Laravel Echo 监听器。

然而,使用 Livewire,你可以使用 #[On] 属性直接在组件中监听它:

php
<?php // resources/views/components/⚡dashboard.blade.php

use Livewire\Attributes\On;
use Livewire\Component;

new class extends Component {
    public $showNewOrderNotification = false;

    #[On('echo:orders,OrderShipped')]
    public function onOrderShipped()
    {
        $this->showNewOrderNotification = true;
    }
};

上面组件中的 echo: 前缀告诉 Livewire 你正在监听 Echo 事件。在 echo: 后面,提供频道和事件名称。在此示例中,我们监听"orders"频道和"OrderShipped"事件。

现在当后端分发 OrderShipped 事件时,Livewire 组件将接收它并调用 onOrderShipped() 方法。

私有频道

要监听私有频道,在频道名称前加上 private-

php
#[On('echo-private:orders.{orderId},OrderShipped')]

访问事件负载

你可以使用方法的第一个参数访问广播事件的负载:

php
#[On('echo:orders,OrderShipped')]
public function onOrderShipped($payload)
{
    $orderId = $payload['orderId'];
}

动态频道名称

你可以使用 {paramName} 语法订阅动态频道名称:

php
#[On('echo-private:orders.{orderId},OrderShipped')]

另请参阅

  • 嵌套 — 嵌套组件之间的通信
  • 操作 — 从组件调用方法
  • Alpine — 与 JavaScript 事件集成
  • 测试 — 测试事件分发和监听