主题
事件
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>改进性能
上面的示例需要两个网络请求来完成删除帖子和刷新父列表:
- 当子组件的
delete操作被调用时第一个请求 - 父组件监听
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"事件时关闭模态框。
需要记住两件重要的事情,以了解事件将如何被接收:
- 由于事件冒泡到
window,在 Alpine 监听器中需要.window修饰符才能正确捕获它 - 如果事件传递了参数,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 指令,如 @js 或 Echo.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')]