Перейти к содержанию

Морфинг⚓︎

Когда компонент 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:

'inject_morph_markers' => false,

Оборачивание условий⚓︎

Если два вышеуказанных решения не подходят для вашей ситуации, наиболее надежный способ избежать проблем с морфингом — оборачивать условия и циклы в их собственные элементы, которые присутствуют всегда.

Например, вот тот же шаблон 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 — обход морфинга для конкретных элементов