Понимание вложенности⚓︎
Как и во многих других фреймворках, основанных на компонентах, в Livewire компоненты можно вкладывать друг в друга — то есть один компонент может отрисовывать внутри себя несколько других компонентов.
Однако система вложенности в 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">Обновить пост</button>
</div>
Вот как может выглядеть HTML-код всего дерева компонентов при начальной загрузке страницы:
<div wire:id="123" wire:snapshot="...">
Лимит постов: <input type="number" wire:model.live="postLimit">
<div wire:id="456" wire:snapshot="...">
<h1>Первый пост</h1>
<p>Содержание поста</p>
<button wire:click="$refresh">Обновить пост</button>
</div>
<div wire:id="789" wire:snapshot="...">
<h1>Второй пост</h1>
<p>Содержание поста</p>
<button wire:click="$refresh">Обновить пост</button>
</div>
</div>
Обратите внимание, что родительский компонент содержит как свой собственный отрисованный шаблон, так и отрисованные шаблоны всех вложенных в него компонентов.
Поскольку каждый компонент независим, у каждого из них есть свои собственные ID и снимки состояния (wire:id и wire:snapshot), встроенные в их HTML, чтобы ядро JavaScript Livewire могло их извлечь и отслеживать.
Давайте рассмотрим несколько различных сценариев обновления, чтобы увидеть различия в том, как Livewire обрабатывает разные уровни вложенности.
Обновление дочернего компонента⚓︎
Если вы нажмете кнопку «Обновить пост» в одном из дочерних компонентов show-post, вот что будет отправлено на сервер:
А вот какой HTML будет отправлен обратно:
<div wire:id="456">
<h1>Первый пост</h1>
<p>Содержимое поста</p>
<button wire:click="$refresh">Обновить пост</button>
</div>
Здесь важно отметить, что когда обновление инициируется в дочернем компоненте, на сервер отправляются только данные этого компонента, и перерисовывается только этот компонент.
Теперь давайте посмотрим на менее интуитивный сценарий: обновление родительского компонента.
Обновление родительского компонента⚓︎
Напомним, вот шаблон Blade родительского компонента Posts:
<div>
Лимит постов: <input type="number" wire:model.live="postLimit">
@foreach ($posts as $post)
<livewire:show-post :$post :wire:key="$post->id">
@endforeach
</div>
Если пользователь изменит значение «Лимит постов» с 2 на 1, обновление будет инициировано исключительно для родительского компонента.
Вот пример того, как может выглядеть полезная нагрузка запроса:
{
updates: { postLimit: 1 },
snapshot: {
memo: { name: 'posts', id: '123' },
state: { postLimit: 2, ... },
},
}
Как видите, на сервер отправляется только снимок родительского компонента Posts.
Важный вопрос, который вы могли себе задать: что происходит, когда родительский компонент перерисовывается и встречает дочерние компоненты show-post? Как он перерисует дочерние элементы, если их снимки состояния не были включены в запрос?
Ответ: они не будут перерисованы.
Когда Livewire отрисовывает компонент Posts, он создает заглушки (плейсхолдеры) для любых дочерних компонентов, которые он встречает.
Вот пример того, каким может быть отрисованный HTML для компонента Posts после вышеуказанного обновления:
<div wire:id="123">
Лимит постов: <input type="number" wire:model.live="postLimit">
<div wire:id="456"></div>
</div>
Как видите, отрисован только один дочерний элемент, потому что postLimit был обновлен до 1. Однако вы также заметите, что вместо полного дочернего компонента есть только пустой <div></div> с соответствующим атрибутом wire:id.
Когда этот HTML будет получен на фронтенде, Livewire применит алгоритм morph (трансформации) старого HTML этого компонента в этот новый HTML, но при этом интеллектуально пропустит любые заглушки дочерних компонентов.
В результате после трансформации конечное содержимое DOM родительского компонента Posts будет следующим:
<div wire:id="123">
Лимит постов: <input type="number" wire:model.live="postLimit">
<div wire:id="456">
<h1>Первый пост</h1>
<p>Содержимое поста</p>
<button wire:click="$refresh">Обновить пост</button>
</div>
</div>
Влияние на производительность⚓︎
Независимая архитектура компонентов Livewire может иметь как положительные, так и отрицательные последствия для вашего приложения.
Преимущество такой архитектуры в том, что она позволяет изолировать ресурсозатратные части вашего приложения. Например, вы можете вынести медленный запрос к базе данных в отдельный независимый компонент, и его нагрузка на производительность не повлияет на остальную часть страницы.
Однако самым большим недостаком этого подхода является то, что, поскольку компоненты полностью разделены, взаимодействие и зависимости между компонентами усложняются.
Например, если бы свойство передавалось от вышеуказанного родительского компонента Posts во вложенный компонент ShowPost, оно не было бы «реактивным». Поскольку каждый компонент независим, если запрос к родительскому компоненту изменит значение свойства, передаваемого в ShowPost, оно не обновится внутри ShowPost.
Livewire преодолел ряд этих препятствий и предоставляет специальные API для таких сценариев, как: реактивные свойства, связываемые компоненты (modelable) и объект $parent.
Вооружившись этими знаниями о том, как работают вложенные компоненты Livewire, вы сможете принимать более обоснованные решения о том, когда и как вкладывать компоненты в вашем приложении.