嵌套
与许多其他基于组件的框架一样,Livewire 组件可以嵌套——即一个组件可以在自身内部渲染多个组件。
不过,由于 Livewire 的嵌套系统与其他框架的构建方式不同,有一些影响与约束需要了解。
每个组件都是独立的
在 Livewire 中,页面上每个组件都独立跟踪自己的状态并独立发起更新。
例如,看下面的 Posts 与嵌套的 ShowPost 组件:
<?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Posts extends Component
{
public $postLimit = 2;
public function render()
{
return view('livewire.posts', [
'posts' => Auth::user()->posts()
->limit($this->postLimit)->get(),
]);
}
}<div>
Post Limit: <input type="number" wire:model.live="postLimit">
@foreach ($posts as $post)
<livewire:show-post :$post :wire:key="$post->id">
@endforeach
</div><?php
namespace App\Livewire;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;
class ShowPost extends Component
{
public Post $post;
public function render()
{
return view('livewire.show-post');
}
}<div>
<h1>{{ $post->title }}</h1>
<p>{{ $post->content }}</p>
<button wire:click="$refresh">Refresh post</button>
</div>整个组件树在页面初始加载时的 HTML 可能如下:
<div wire:id="123" wire:snapshot="...">
Post Limit: <input type="number" wire:model.live="postLimit">
<div wire:id="456" wire:snapshot="...">
<h1>The first post</h1>
<p>Post content</p>
<button wire:click="$refresh">Refresh post</button>
</div>
<div wire:id="789" wire:snapshot="...">
<h1>The second post</h1>
<p>Post content</p>
<button wire:click="$refresh">Refresh post</button>
</div>
</div>注意:父组件同时包含自身渲染的模板,以及其内部所有嵌套组件渲染的模板。
由于每个组件都是独立的,它们各自在 HTML 中嵌入自己的 ID 与快照(wire:id 和 wire:snapshot),供 Livewire 的 JavaScript 核心提取与跟踪。
下面看几种不同的更新场景,了解 Livewire 处理不同嵌套层级时的差异。
更新子组件
若点击某个子组件 show-post 中的「Refresh post」按钮,发往服务器的内容如下:
{
memo: { name: 'show-post', id: '456' },
state: { ... },
}返回的 HTML 如下:
<div wire:id="456">
<h1>The first post</h1>
<p>Post content</p>
<button wire:click="$refresh">Refresh post</button>
</div>这里的关键是:在子组件上触发更新时,只有该组件的数据会发往服务器,也只有该组件会重新渲染。
再看一个不那么直观的场景:更新父组件。
更新父组件
提醒一下,父组件 Posts 的 Blade 模板如下:
<div>
Post Limit: <input type="number" wire:model.live="postLimit">
@foreach ($posts as $post)
<livewire:show-post :$post :wire:key="$post->id">
@endforeach
</div>若用户把「Post Limit」从 2 改成 1,只会在父组件上触发更新。
请求载荷可能如下:
{
updates: { postLimit: 1 },
snapshot: {
memo: { name: 'posts', id: '123' },
state: { postLimit: 2, ... },
},
}可以看到,只有父组件 Posts 的快照会发往服务器。
你可能会问:父组件重新渲染并遇到子组件 show-post 时会发生什么?若请求载荷里没有子组件的快照,它要如何重新渲染子组件?
答案是:它们不会被重新渲染。
Livewire 渲染 Posts 组件时,对遇到的任何子组件只会渲染占位符。
上述更新后,Posts 组件渲染出的 HTML 可能如下:
<div wire:id="123">
Post Limit: <input type="number" wire:model.live="postLimit">
<div wire:id="456"></div>
</div>可以看到,因为 postLimit 更新为 1,只渲染了一个子组件。同时你会注意到:不是完整的子组件,而只是一个带匹配 wire:id 的空 <div></div>。
前端收到这段 HTML 后,Livewire 会把该组件的旧 HTML 变形 为新 HTML,但会智能跳过任何子组件占位符。
效果是:变形 之后,父组件 Posts 最终的 DOM 内容如下:
<div wire:id="123">
Post Limit: <input type="number" wire:model.live="postLimit">
<div wire:id="456">
<h1>The first post</h1>
<p>Post content</p>
<button wire:click="$refresh">Refresh post</button>
</div>
</div>性能影响
Livewire 的独立组件架构对应用既有正面也有负面影响。
优点是可以把应用中开销大的部分隔离出去。例如,可以把慢查询隔离到独立组件中,其性能开销不会影响页面其余部分。
最大的缺点是:组件完全分离后,组件间通信/依赖会更困难。
例如,若从上面的父组件 Posts 把属性传给嵌套的 ShowPost,它默认不是「响应式」的。因为每个组件都独立,父组件请求若改变了传入 ShowPost 的属性值,ShowPost 内部不会随之更新。
Livewire 已克服其中不少障碍,并为这些场景提供了专用 API,例如:响应式属性、可绑定模型的组件,以及 $parent 对象。
了解嵌套 Livewire 组件的工作方式后,你就能更明智地决定何时以及如何在应用中嵌套组件。