Skip to content
全部文档

嵌套

与许多其他基于组件的框架一样,Livewire 组件可以嵌套——即一个组件可以在自身内部渲染多个组件。

不过,由于 Livewire 的嵌套系统与其他框架的构建方式不同,有一些影响与约束需要了解。

TIP

请先理解水合

在深入了解 Livewire 的嵌套系统之前,最好先充分理解 Livewire 如何水合组件。可阅读水合文档了解更多。

每个组件都是独立的

在 Livewire 中,页面上每个组件都独立跟踪自己的状态并独立发起更新。

例如,看下面的 Posts 与嵌套的 ShowPost 组件:

php
<?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(),
        ]);
    }
}
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>
php
<?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');
    }
}
blade
<div>
    <h1>{{ $post->title }}</h1>

    <p>{{ $post->content }}</p>

    <button wire:click="$refresh">Refresh post</button>
</div>

整个组件树在页面初始加载时的 HTML 可能如下:

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:idwire:snapshot),供 Livewire 的 JavaScript 核心提取与跟踪。

下面看几种不同的更新场景,了解 Livewire 处理不同嵌套层级时的差异。

更新子组件

若点击某个子组件 show-post 中的「Refresh post」按钮,发往服务器的内容如下:

js
{
    memo: { name: 'show-post', id: '456' },

    state: { ... },
}

返回的 HTML 如下:

html
<div wire:id="456">
    <h1>The first post</h1>

    <p>Post content</p>

    <button wire:click="$refresh">Refresh post</button>
</div>

这里的关键是:在子组件上触发更新时,只有该组件的数据会发往服务器,也只有该组件会重新渲染。

再看一个不那么直观的场景:更新父组件。

更新父组件

提醒一下,父组件 Posts 的 Blade 模板如下:

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,只会在父组件上触发更新。

请求载荷可能如下:

js
{
    updates: { postLimit: 1 },

    snapshot: {
        memo: { name: 'posts', id: '123' },

        state: { postLimit: 2, ... },
    },
}

可以看到,只有父组件 Posts 的快照会发往服务器。

你可能会问:父组件重新渲染并遇到子组件 show-post 时会发生什么?若请求载荷里没有子组件的快照,它要如何重新渲染子组件?

答案是:它们不会被重新渲染。

Livewire 渲染 Posts 组件时,对遇到的任何子组件只会渲染占位符。

上述更新后,Posts 组件渲染出的 HTML 可能如下:

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 内容如下:

html
<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 组件的工作方式后,你就能更明智地决定何时以及如何在应用中嵌套组件。