Livewire 4: однофайловые компоненты на практике — islands, новый роутинг и optimistic UI — PROG-TIME

Livewire 4: однофайловые компоненты на практике — islands, новый роутинг и optimistic UI

15.06.2026

В январе 2026 года вышел Livewire 4 — самый крупный релиз фреймворка с момента его появления. Главное изменение касается того, как мы вообще пишем компоненты: теперь PHP-логика, Blade-шаблон, CSS и JavaScript живут в одном файле. Это уже не Volt-эксперимент, а поведение по умолчанию. В этой статье разберём однофайловые компоненты (single-file components, SFC) на практике, а заодно посмотрим на islands, новый роутинг и optimistic UI.

Установка и первый однофайловый компонент

Livewire 4 ставится привычной командой Composer и требует Laravel 11+ и PHP 8.2+:

composer require livewire/livewire:^4.0
php artisan optimize:clear

Теперь создадим компонент. Команда make:livewire по умолчанию генерирует именно однофайловый компонент:

php artisan make:livewire counter

В результате появится файл resources/views/components/⚡counter.blade.php. Да, эмодзи-молния в имени — это не опечатка: Livewire помечает ей свои компоненты, чтобы их было видно в дереве файлов с первого взгляда. Если эмодзи мешают, их можно отключить в конфиге (make_command.emoji => false). Сам файл выглядит так:

<?php // resources/views/components/⚡counter.blade.php

use Livewire\Component;

new class extends Component {
    public $count = 0;

    public function increment()
    {
        $this->count++;
    }
};
?>

<div>
    <h1>{{ $count }}</h1>
    <button wire:click="increment">+</button>
</div>

Класс объявляется анонимно прямо в шаблоне через new class extends Component — тот же синтаксис, что раньше предлагал Volt. Для крупных компонентов остаётся многофайловый формат (MFC): каталог с отдельными файлами counter.php, counter.blade.php, опциональными counter.css, counter.js и counter.test.php. Создаётся он флагом --mfc, а конвертировать форматы туда-обратно можно командой php artisan livewire:convert.

Роутинг через Route::livewire() и namespaces

Классический способ Route::get('/dashboard', Dashboard::class) всё ещё работает, но для SFC и MFC рекомендуется новый метод Route::livewire(), который ссылается на компонент по имени, а не по классу:

use Illuminate\Support\Facades\Route;

Route::livewire('/dashboard', 'pages::dashboard');

Livewire 4 приносит мнение об устройстве проекта: по умолчанию доступны два namespace — pages:: для страничных компонентов и layouts:: для макетов, а всё остальное лежит в resources/views/components рядом с обычными Blade-компонентами. Для модульных приложений можно регистрировать собственные namespace (например, admin:: или billing::) через ключ component_namespaces в config/livewire.php.

Scoped CSS и JavaScript внутри компонента

В однофайловом компоненте стили и скрипты пишутся прямо в шаблоне. Стили автоматически изолируются — класс .title не «утечёт» в остальную страницу. Если нужны глобальные стили, добавляется атрибут <style global>:

<div>
    <h1 class="title">{{ $count }}</h1>
    <button wire:click="$js.celebrate">+</button>
</div>

<style>
.title {
    color: blue;
    font-size: 2rem;
}
</style>

<script>
    this.$js.celebrate = () => {
        confetti()
    }
</script>

Важная деталь производительности: и CSS, и JS отдаются браузеру как отдельные нативные .css/.js-файлы и кешируются. Внутри скрипта this — это алиас для $wire, через который доступен контекст компонента.

Islands — изолированный рендеринг

Islands — флагманская фича четвёртой версии. Они позволяют выделить внутри компонента область, которая обновляется независимо от остального шаблона:

<div>
    @island
        <div>
            Выручка: {{ $this->revenue }}
            <button wire:click="$refresh">Обновить</button>
        </div>
    @endisland

    <div>
        <!-- Этот блок не перерисуется при обновлении острова -->
        Остальной контент...
    </div>
</div>

Выигрыш не только в DOM. Если остров завязан на computed-свойство, то при его обновлении выполняются только запросы этого острова — изоляция тянется от базы данных до отрендеренного HTML. Острова поддерживают ленивую загрузку (@island(lazy: true)), именование для адресации (name: 'revenue') и дозагрузку контента для бесконечной прокрутки через wire:island.append.

Optimistic UI и состояния загрузки

Чтобы интерфейс ощущался мгновенным, в Livewire 4 появилась группа директив, обновляющих страницу без обращения к серверу. wire:show переключает видимость через CSS (без удаления из DOM и сетевого запроса), wire:text мгновенно меняет текст, а wire:bind реактивно привязывает любой HTML-атрибут:

<div wire:show="showModal">...</div>

Лайки: <span wire:text="likes"></span>

<input wire:model="message"
       wire:bind:class="message.length > 240 && 'text-red-500'">

Отдельно стоит отметить автоматический атрибут data-loading: любой элемент, который инициирует сетевой запрос, получает его на время выполнения. Это позволяет стилизовать загрузку прямо из CSS, в том числе через утилиты Tailwind:

<button wire:click="save" class="data-loading:opacity-50">
    Сохранить
</button>

Drag-and-drop без сторонних библиотек

Сортировка перетаскиванием теперь встроена через директиву wire:sort — внешние библиотеки не нужны, анимации обрабатываются автоматически:

<ul wire:sort="reorder">
    @foreach ($items as $item)
        <li wire:key="{{ $item->id }}" wire:sort:item="{{ $item->id }}">
            {{ $item->title }}
        </li>
    @endforeach
</ul>
public function reorder($item, $position)
{
    // $item — это ID, $position — новый индекс
}

Доступны ручки перетаскивания (wire:sort:handle), исключение интерактивных элементов (wire:sort:ignore) и перетаскивание между списками (wire:sort:group).

Миграция с Volt

Поскольку SFC используют тот же синтаксис, что и классовые компоненты Volt, переход почти бесшовный. В компонентах замените импорт Livewire\Volt\Component на Livewire\Component, в маршрутах — Volt::route() на Route::livewire(), в тестах — Volt::test() на Livewire::test(). После этого удалите VoltServiceProvider из bootstrap/providers.php и сам пакет:

composer remove livewire/volt

Существующие классовые Volt-компоненты заработают без изменений, потому что их синтаксис совпадает с нативными SFC.

Итоги

Livewire 4 смещает дефолты в сторону меньшего количества бойлерплейта: один файл на компонент, scoped-стили и скрипты «из коробки», нативный роутинг через Route::livewire() и реально полезные фичи вроде islands, drag-and-drop и optimistic UI. При этом обратная совместимость сохранена — классовые компоненты и старый роутинг продолжают работать, так что обновляться можно постепенно. Если вы уже используете Volt, миграция сводится к нескольким заменам импортов и удалению пакета. А начать знакомство проще всего с одной команды make:livewire — и сразу писать компонент целиком в одном месте.