Skip to content

深入理解嵌套

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

但是,由于 Livewire 的嵌套系统的构建方式与其他框架不同,因此有一些重要的影响和约束需要注意。

确保你首先了解水合

在了解有关 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 :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>

请注意,父组件包含其渲染的模板和嵌套在其中的所有组件的渲染模板。

因为每个组件都是独立的,所以它们各自都有自己的 ID 和快照(wire:idwire:snapshot)嵌入在它们的 HTML 中,供 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 :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。但是,你还会注意到,完整的子组件被替换为只有一个空的 <div></div>,带有匹配的 wire:id 属性。

当前端接收到此 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 组件如何运行的知识,你将能够就何时以及如何在应用程序中嵌套组件做出更明智的决定。