Livewire 3 и 4 — что изменилось

Всё, что изменилось между Livewire 3 и Livewire 4: новые возможности, которые стоит взять, каждое ломающее изменение с кодом «было / стало» и чек-лист самого апгрейда. Включая три изменения, которые проходят ваши тесты и ломают продакшен.

Livewire 3 → 4 Ломающие изменения Чек-лист миграции 14 разделов

1. Коротко

Livewire 4 — большой релиз с небольшим путём миграции. Модель компонента не изменилась: PHP-класс, Blade-шаблон, состояние на сервере, HTML по проводу. Изменилось то, где лежат компоненты, как они пишутся и какую часть страницы задевает одно обновление.

Классовые компоненты продолжают работать. Однофайловый формат — умолчание для новых компонентов, а не принудительное переписывание. Большинство приложений на Livewire 3 обновляются так: поднять версию, починить горстку ломающих изменений и постепенно осваивать новые возможности.

Что вы получаете

  • Однофайловые компоненты — класс и разметка в одном файле.
  • Острова — области с независимым рендерингом, без разбиения на дочерние компоненты.
  • Слоты — родитель передаёт разметку в потомка, вычисляется в контексте родителя.
  • Оптимистичный UIwire:show, wire:text, wire:bind меняют DOM без round trip.
  • Drag and dropwire:sort, без внешней библиотеки.
  • Скоупленный CSS — стили компонента, которые не протекают.

Что подставит

  1. Незакрытые теги <livewire:…> молча не рендерятся.
  2. Модификаторы wire:model изменили смысл — поля перестают синхронизироваться.
  3. Эндпоинт обновлений переехал на /livewire-{hash}/ — ломаются инфраструктурные правила.
  4. Модификаторы wire:transition убраны.
  5. Volt вошёл в ядро.

Каждый пункт разобран ниже с кодом «было / стало».

2. Сравнение бок о бок

ОбластьLivewire 3Livewire 4
Файлы компонента app/Livewire/Counter.php + resources/views/livewire/counter.blade.php resources/views/components/⚡counter.blade.php (один файл)
Команда создания php artisan livewire:make Counter php artisan make:livewire counter
Маршрутизация Route::get('/x', Dashboard::class) Route::livewire('/x', 'pages::dashboard')
Частичный рендеринг Разбиение на дочерние компоненты Области @island
Слоты Не поддерживаются {{ $slot }}, <wire:slot name="…">
Drag and drop SortableJS плюс обвязка wire:sort
Мгновенный UI Alpine.js руками wire:show, wire:text, wire:bind
CSS компонента Глобальный стайлшит Скоупленный <style> в компоненте
Параллельные действия Недоступны #[Async], wire:click.async
Эндпоинт обновлений /livewire/update /livewire-{hash}/update
JS-хуки Livewire.hook('request'|'commit') interceptRequest(), interceptMessage()
Volt Отдельный пакет Вошёл в ядро
Тестирование PHPUnit или Pest, tests/Feature/Livewire Рекомендуется Pest, тесты могут лежать рядом с компонентом

3. Апгрейд по шагам

Bash
# 1. Поднять зависимость
composer require livewire/livewire:^4.0

# 2. Очистить все кеши — устаревшие скомпилированные шаблоны это типовая первая поломка
php artisan optimize:clear

# 3. Прогнать тесты, прежде чем трогать что-то ещё
php artisan test

Laravel Shift автоматизирует значительную часть механической работы. Независимо от того, пользуетесь вы им или нет, пройдитесь потом по чек-листу ниже — молчаливые поломки инструмент отмечает хуже всего.

Порядок работ, который обычно проходит гладко

  1. Обновить пакет и очистить кеши.
  2. Починить переименования в конфиге (глава 4).
  3. Починить маршруты (глава 5).
  4. Найти в коде незакрытые теги компонентов (глава 6).
  5. Проверить каждый wire:model с модификатором (глава 7).
  6. Обновить инфраструктурные правила под новый эндпоинт (глава 8).
  7. Заменить убранные модификаторы wire:transition (глава 9).
  8. Мигрировать компоненты Volt, если он использовался (глава 12).
  9. Прогнать тесты снова и пройти приложение руками в браузере.
  10. И только потом браться за острова, слоты и однофайловые компоненты.
Не совмещайте апгрейд с переписыванием. Сначала добейтесь зелёного приложения на v4 с существующими классовыми компонентами. Перевод на однофайловый формат и острова — отдельная работа со своим ревью.

4. Переименования в конфигурации

Livewire 3Livewire 4Примечание
layoutcomponent_layoutИспользует namespace layouts::
lazy_placeholdercomponent_placeholder
smart_wire_keyssmart_wire_keysУмолчание сменилось с false на true
component_locationsКаталоги, где ищутся компоненты
component_namespacesИменованные корни, например pages::
make_commandОднофайловая или классовая генерация
csp_safeСборка под строгую CSP
Livewire 3 — config/livewire.php
'layout' => 'layouts.app',

'lazy_placeholder' => 'livewire.placeholder',
Livewire 4 — config/livewire.php
'component_layout' => 'layouts::app',

'component_placeholder' => 'livewire.placeholder',

'component_locations' => [
    'resources/views/components',
    'resources/views/livewire',
],

'component_namespaces' => [
    'pages' => 'resources/views/pages',
],

// Продолжать генерировать классовые компоненты
'make_command' => [
    'type' => 'class',
],

5. Маршрутизация

Для full-page компонентов появился отдельный макрос маршрута.

Livewire 3
use App\Livewire\Dashboard;

Route::get('/dashboard', Dashboard::class)
    ->middleware('auth')
    ->name('dashboard');
Livewire 4
use App\Livewire\Dashboard;

// По классу
Route::livewire('/dashboard', Dashboard::class)
    ->middleware('auth')
    ->name('dashboard');

// Или по имени компонента, для view-based компонентов
Route::livewire('/dashboard', 'pages::dashboard')
    ->middleware('auth')
    ->name('dashboard');
Быстрый способ найти все: grep -rn "Route::get(.*::class" routes/, затем проверить, какие из этих классов наследуют Livewire\Component.

6. Теги компонентов обязаны закрываться

Самое неприятное, потому что ломается молча. Незакрытый тег компонента не рендерит ничего — ни исключения, ни записи в логе, просто пропавший блок на странице.
Livewire 3 — допускалось
<livewire:user-profile>
<livewire:order-list>
Livewire 4 — обязательно
<livewire:user-profile />
<livewire:order-list />

Как найти

Bash
# Теги компонентов, которые не самозакрыты
grep -rn "<livewire:[^>]*[^/]>" resources/views/

# Форма через директиву @livewire() не затронута
grep -rn "@livewire(" resources/views/

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

7. wire:model изменился дважды

Два независимых изменения, и оба дают «поле просто перестало работать» вместо ошибки. Проверьте каждый wire:model с модификатором.

7.1 Модификаторы управляют синхронизацией на клиенте

В v3 .blur и .change решали, когда уходит сетевой запрос. Само значение всегда отслеживалось на клиенте. В v4 они решают, когда значение вообще синхронизируется. Чтобы вернуть прежнее поведение, добавьте .live.

Livewire 3
<input wire:model.blur="title">
<select wire:model.change="status">
Livewire 4 — то же поведение
<input wire:model.live.blur="title">
<select wire:model.live.change="status">

7.2 Больше нет всплытия от дочерних элементов

wire:model на обёртке больше не перехватывает события, всплывающие от вложенных полей.

Livewire 3 — обёртка ловила
<div wire:model="value">
    <input type="text">
</div>
Livewire 4 — включаем явно
<div wire:model.deep="value">
    <input type="text">
</div>

Как найти

Bash
# Все wire:model с модификатором
grep -rn "wire:model\.[a-z]" resources/views/

# wire:model на не-input элементе — кандидат на проблему со всплытием
grep -rn "<div[^>]*wire:model" resources/views/
Кастомные компоненты форм — датапикер, редактор, обёрнутый select — страдают от изменения всплытия чаще всего, потому что паттерн обёртки это ровно то, как они обычно устроены.

8. Эндпоинт обновлений переехал

Все URL Livewire теперь содержат хеш: /livewire/ стал /livewire-{hash}/.

Это изменение проходит все тесты и ломает продакшен. Ничто в вашем PHP-коде не ссылается на путь, поэтому набор тестов остаётся зелёным. Поломка проявляется только там, где в инфраструктуре прописан буквальный /livewire/.

Где проверить

  • Блоки location в nginx или Apache
  • Белые списки WAF и файрвола
  • Правила обхода кеша CDN (page rules в Cloudflare и подобное)
  • Rate limiter'ы, привязанные к пути
  • Правила маршрутизации балансировщика
  • Мониторинг и uptime-проверки, нацеленные на эндпоинт
  • Записи connect-src в CSP, привязанные к пути
nginx — было
location /livewire/ {
    # …
}
nginx — стало
location ~ ^/livewire(-[a-z0-9]+)?/ {
    # …
}

9. wire:transition переделан

wire:transition больше не принимает модификаторы вроде .opacity, .scale или .duration.200ms. Он построен на нативном View Transitions API, а анимация описывается в CSS.

Livewire 3
<div wire:transition.opacity.duration.300ms>
    …
</div>
Livewire 4
<div wire:transition>
    …
</div>
CSS
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 300ms;
}
Поддержка View Transitions в браузерах неполная. Там, где её нет, содержимое просто появляется без анимации — деградация мягкая, но знать об этом стоит, если эффект нужен вам для понятности, а не для красоты.

10. Изменения в PHP API

Стриминг

Livewire 3
$this->stream(to: '#container', content: 'Hello', replace: true);
Livewire 4
$this->stream('Hello', replace: true, el: '#container');

Монтирование компонента

Livewire 3
mount($name, $params = [], $key = null)
Livewire 4
mount($name, $params = [], $key = null, $slots = [])

Актуально, если вы монтируете компоненты программно или у вас есть свой код-обёртка над монтированием Livewire. Обычный прикладной код это не затрагивает.

Новые атрибуты, о которых стоит знать

АтрибутНазначение
#[Async]Выполнить действие параллельно, вне очереди запросов
#[Renderless]Пропустить перерисовку после действия
#[Prop]Объявить свойство как props от родителя
#[Json]Вернуть данные прямо в JavaScript

11. Изменения в JavaScript API

Действия $js

Livewire 3
$wire.$js('showToast', { message: 'Сохранено' });
Livewire 4
$wire.$js.showToast = { message: 'Сохранено' };

Хуки стали перехватчиками

Хуки commit и request объявлены устаревшими в пользу interceptMessage() и interceptRequest().

Livewire 3
document.addEventListener('livewire:init', () => {
    Livewire.hook('request', ({ options, fail }) => {
        options.headers['X-Tenant'] = window.tenantId;

        fail(({ status, preventDefault }) => {
            if (status === 419) {
                preventDefault();
                window.location.reload();
            }
        });
    });
});
Livewire 4
document.addEventListener('livewire:init', () => {
    Livewire.interceptRequest(({ options, fail }) => {
        options.headers['X-Tenant'] = window.tenantId;

        fail(({ status, preventDefault }) => {
            if (status === 419) {
                preventDefault();
                window.location.reload();
            }
        });
    });

    Livewire.interceptMessage(({ component, succeed }) => {
        succeed(() => {
            console.debug('компонент обновлён', component.name);
        });
    });
});

Новые магические свойства

Blade
{{-- Ошибки валидации на клиенте --}}
<div x-show="$errors.has('email')" x-text="$errors.first('email')"></div>

12. Volt вошёл в ядро

Однофайловый синтаксис Volt стал прототипом того, чем в Livewire 4 стали однофайловые компоненты. Отдельный пакет больше не нужен.

VoltLivewire 4
Livewire\Volt\ComponentLivewire\Component
Volt::route()Route::livewire()
Volt::test()Livewire::test()
Сервис-провайдер VoltУдалить
Пакет livewire/voltУдалить зависимость
Bash
composer remove livewire/volt
Было (Volt)
<?php

use Livewire\Volt\Component;

new class extends Component {
    public string $title = '';
};

?>

<div>…</div>
Стало (Livewire 4)
<?php

use Livewire\Component;

new class extends Component {
    public string $title = '';
};

?>

<div>…</div>

13. Освоение новых возможностей

Когда приложение зелёное на v4, вот изменения, которые окупаются быстрее всего — примерно в порядке отдачи на усилие.

1. Острова на самой медленной странице

Найдите страницу, где одно маленькое взаимодействие перезапускает кучу запросов — обычно это дашборд или таблица с фильтрами. Оберните независимые области в @island.

Было — перерисовывается весь компонент
<div>
    <div class="card">Выручка: {{ $this->revenue }}</div>
    <div class="card">Задач в очереди: {{ $this->queue }}</div>
    <div class="card">Последняя активность: …</div>
</div>
Стало — каждая карточка обновляется сама
<div>
    @island(name: 'revenue')
        <div class="card">Выручка: {{ $this->revenue }}</div>
    @endisland

    @island(name: 'queue', poll: '5s')
        <div class="card">Задач в очереди: {{ $this->queue }}</div>
    @endisland

    @island(name: 'activity', lazy: true)
        @placeholder
            <div class="card skeleton animate-pulse h-48"></div>
        @endplaceholder

        <div class="card">Последняя активность: …</div>
    @endisland
</div>

2. Заменить round trip клиентскими директивами

Было
<button wire:click="$toggle('showFilters')">Фильтры</button>

@if ($showFilters)
    <div class="filters">…</div>
@endif
Стало — вообще без запроса
<button wire:click="$toggle('showFilters')">Фильтры</button>

<div class="filters" wire:show="showFilters">…</div>

3. Выбросить SortableJS

Стало
<ul wire:sort="reorder">
    @foreach ($tasks as $task)
        <li wire:sort:item="{{ $task->id }}" wire:key="task-{{ $task->id }}">
            <span wire:sort:handle>⠿</span> {{ $task->title }}
        </li>
    @endforeach
</ul>

4. Схлопнуть дочерние компоненты, созданные только ради рендера

В v3 стандартным лекарством от «слишком много перерисовывается» был дочерний компонент. Если у потомка нет собственного состояния и он не переиспользуется, остров в родителе легче — один компонент, один класс, один snapshot.

5. #[Renderless] для невидимой работы

PHP
use Livewire\Attributes\Renderless;

#[Renderless]
public function trackView(): void
{
    $this->post->increment('views');
}

6. Перевод на однофайловый формат — последним и только там, где помогает

Это косметика. Делайте по случаю, когда и так правите файл, а не отдельным спринтом миграции. Мелкие компоненты выигрывают; компоненты с тяжёлой логикой и множеством зависимостей часто понятнее классами.

14. Чек-лист апгрейда

До начала

  • Тесты проходят на Livewire 3.
  • Отдельная ветка и бэкап базы, если окружение общее.
  • Кто-то готов пройти приложение руками после — молчаливые поломки видит только человек.

Механические правки

  • composer require livewire/livewire:^4.0
  • php artisan optimize:clear
  • layoutcomponent_layout (и namespace layouts::)
  • lazy_placeholdercomponent_placeholder
  • Route::get(…, Component::class)Route::livewire(…)
  • Все теги <livewire:…> самозакрыты
  • wire:model.blur / .change → добавить .live
  • wire:model на обёртке → добавить .deep
  • wire:scrollwire:navigate:scroll
  • Модификаторы wire:transition → в CSS
  • Порядок аргументов $this->stream()
  • $wire.$js('name', …)$wire.$js.name = …
  • Livewire.hook('request'|'commit')interceptRequest() / interceptMessage()
  • Классы, маршруты, тесты и пакет Volt удалены

Инфраструктура

  • Location-блоки nginx / Apache расширены под /livewire-{hash}/
  • Правила WAF, CDN и файрвола обновлены
  • Rate limiter'ы и мониторинг перенацелены
  • connect-src в CSP пересмотрен

Проверка

  • Тесты зелёные
  • Все формы отправляются и валидируются
  • Загрузка файлов работает, включая индикатор прогресса
  • Таблицы листаются, сортируются и фильтруются
  • Модальные окна открываются и закрываются
  • Сторонние виджеты внутри wire:ignore инициализируются
  • Консоль браузера чистая на основных сценариях
  • Запросы обновления возвращают 200 на проде, а не 403 от WAF
Выкатывайте канареечно, если есть возможность. Обе поломки, переживающие зелёные тесты — незакрытые теги и переехавший эндпоинт — проявляются на реальном трафике сразу и тривиально откатываются.

Официальные ресурсы

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