Морфинг⚓︎
Когда компонент Livewire обновляет DOM в браузере, он делает это интеллектуальным способом, который мы называем «морфингом». Термин морфинг (morph) используется в противовес слову замена (replace).
Вместо того чтобы заменять HTML-код компонента новым отрисованным HTML при каждом обновлении, Livewire динамически сравнивает текущий HTML с новым, выявляет различия и вносит точечные изменения только в тех местах, где они необходимы.
Это позволяет сохранять существующие, неизменённые элементы компонента. Например, слушатели событий, состояние фокуса и значения полей ввода в формах сохраняются между обновлениями Livewire. Разумеется, морфинг также обеспечивает более высокую производительность по сравнению с полной очисткой и повторной отрисовкой DOM при каждом обновлении.
Как работает морфинг⚓︎
Чтобы понять, как Livewire определяет, какие элементы нужно обновить, рассмотрим простой компонент Todos:
<?php
class Todos extends Component
{
public $todo = '';
public $todos = [
'first',
'second',
];
public function add()
{
$this->todos[] = $this->todo;
}
}
<form wire:submit="add">
<ul>
@foreach ($todos as $item)
<li wire:key="{{ $loop->index }}">{{ $item }}</li>
@endforeach
</ul>
<input wire:model="todo">
</form>
Первоначальная отрисовка этого компонента выведет следующий HTML:
<form wire:submit="add">
<ul>
<li>first</li>
<li>second</li>
</ul>
<input wire:model="todo">
</form>
Теперь представьте, что вы ввели «third» в поле ввода и нажали клавишу [Enter]. Новый отрисованный HTML будет таким:
<form wire:submit="add">
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
<input wire:model="todo">
</form>
Когда Livewire обрабатывает обновление компонента, он морфирует исходный DOM в новый отрисованный HTML. Следующая визуализация поможет вам интуитивно понять, как это работает:
Как видите, Livewire одновременно обходит оба дерева HTML. Встречая каждый элемент в обоих деревьях, он сравнивает их на предмет изменений, добавлений и удалений. Если он обнаруживает различие, то точечно вносит соответствующее изменение.
Недостатки морфинга⚓︎
Ниже приведены сценарии, в которых алгоритмы морфинга не могут правильно определить изменения в деревьях HTML, что вызывает проблемы в приложении.
Вставка промежуточных элементов⚓︎
Рассмотрим следующий Blade-шаблон вымышленного компонента CreatePost:
<form wire:submit="save">
<div>
<input wire:model="title">
</div>
@if ($errors->has('title'))
<div>{{ $errors->first('title') }}</div>
@endif
<div>
<button>Сохранить</button>
</div>
</form>
Если пользователь пытается отправить форму, но возникает ошибка валидации, происходит следующая проблема:
Как видите, когда Livewire встречает новый <div> для сообщения об ошибке, он не знает, изменить ли существующий <div> на месте или вставить новый <div> посередине.
Разберем подробнее, что происходит:
- Livewire встречает первый
<div>в обоих деревьях. Они одинаковы, поэтому он продолжает. - Livewire встречает второй
<div>в обоих деревьях и ошибочно полагает, что это один и тот же<div>, у которого просто изменилось содержимое. Вместо того чтобы вставить сообщение об ошибке как новый элемент, он превращает<button>в сообщение об ошибке. - После ошибочного изменения предыдущего элемента Livewire замечает лишний элемент в конце сравнения. Затем он создает и добавляет элемент после предыдущего.
- В результате элемент, который должен был просто переместиться, уничтожается и создается заново.
Этот сценарий лежит в основе почти всех ошибок, связанных с морфингом.
Вот некоторые негативные последствия этих ошибок:
- Слушатели событий и состояние элементов теряются между обновлениями.
- Слушатели событий и состояние ошибочно применяются не к тем элементам.
- Целые компоненты Livewire могут быть сброшены или дублированы, так как они также являются элементами в дереве DOM.
- Состояние и компоненты Alpine могут быть потеряны или перепутаны.
К счастью, Livewire проделал большую работу для смягчения этих проблем, используя следующие подходы:
Внутренняя опережающая проверка (look-ahead)⚓︎
В алгоритме морфинга Livewire есть дополнительный шаг, который проверяет последующие элементы и их содержимое перед изменением текущего элемента.
Это предотвращает возникновение описанного выше сценария во многих случаях.
Вот визуализация работы алгоритма «look-ahead»:
Внедрение маркеров морфинга⚓︎
На стороне сервера Livewire автоматически обнаруживает условные конструкции внутри шаблонов Blade и оборачивает их в маркеры в виде HTML-комментариев. JavaScript-часть Livewire использует их как ориентир при морфинге.
Вот пример предыдущего шаблона Blade, но с внедрёнными маркерами Livewire:
<form wire:submit="save">
<div>
<input wire:model="title">
</div>
<!--[if BLOCK]><![endif]-->
@if ($errors->has('title'))
<div>Error: {{ $errors->first('title') }}</div>
@endif
<!--[if ENDBLOCK]><![endif]-->
<div>
<button>Сохранить</button>
</div>
</form>
С этими маркерами Livewire гораздо проще отличить изменение от добавления.
Эта функция крайне полезна, но поскольку она требует парсинга шаблонов с помощью регулярных выражений, иногда она может неправильно определять условия. Если эта функция скорее мешает, чем помогает вашему приложению, вы можете отключить её в файле конфигурации config/livewire.php:
Оборачивание условий⚓︎
Если два вышеуказанных решения не подходят для вашей ситуации, наиболее надежный способ избежать проблем с морфингом — оборачивать условия и циклы в их собственные элементы, которые присутствуют всегда.
Например, вот тот же шаблон Blade, переписанный с использованием обёрточных элементов <div>:
<form wire:submit="save">
<div>
<input wire:model="title">
</div>
<div>
@if ($errors->has('title'))
<div>{{ $errors->first('title') }}</div>
@endif
</div>
<div>
<button>Сохранить</button>
</div>
</form>
Теперь, когда условие обернуто в постоянный элемент, Livewire правильно выполнит морфинг двух разных деревьев HTML.
Обход морфинга⚓︎
Если вам нужно полностью обойти морфинг для какого-либо элемента, вы можете использовать директиву wire:replace. Она указывает Livewire заменять всех дочерних элементов вместо попытки морфинга существующих.
Смотрите также⚓︎
- Гидратация — жизненный цикл запроса Livewire
- Компоненты — как компоненты отрисовываются и обновляются
- wire:replace — обход морфинга для конкретных элементов