Livewire 4 наконец-то здесь, и это самое масштабное обновление на сегодняшний день.
Речь идет не о добавлении сложности — речь о более удобных настройках по умолчанию, меньшем количестве препятствий и более мощных инструментах для создания именно того, что вам нужно. Мы месяцами с головой погружались в переосмысление того, какими должны быть компоненты Livewire, и мы гордимся полученным результатом.
Давайте посмотрим.
Компоненты на основе представления (View-based components)
Самое заметное изменение в Livewire 4 — это то, как вы пишете компоненты. Теперь вместо того, чтобы переключаться между PHP-классом и Blade-файлом, вы можете поместить всё в один файл:
<?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>
<style>
/* Scoped CSS... */
</style>
<script>
/* Component JavaScript... */
</script>
Теперь это стандартный формат, создаваемый при выполнении команды php artisan make:livewire. Символ молнии (⚡) делает компоненты Livewire мгновенно узнаваемыми в дереве ваших файлов — вы сможете с первого взгляда отличить компонент Livewire от компонента Blade. (Вы можете отключить это, если не любите эмодзи.)
Для больших компонентов
Для крупных компонентов также доступен многофайловый формат, который хранит всё связанное вместе в одной директории:
⚡counter/ ├── counter.php ├── counter.blade.php ├── counter.css (опционально) ├── counter.js (опционально) └── counter.test.php (опционально)
Создавайте такие компоненты с флагом --mfc. Конвертировать между форматами можно в любое время с помощью команды php artisan livewire:convert.
Маршрутизация (Routing)
Теперь компоненты можно указывать одинаково во всех местах. Livewire 4 представляет Route::livewire():
// Было (v3) - всё ещё поддерживается
Route::get('/posts/create', CreatePost::class);
// Стало (v4)
Route::livewire('/posts/create', 'pages::post.create');
Новый синтаксис ссылается на компоненты по имени, а не по классу. Это согласуется с тем, как вы рендерите компоненты во всех остальных частях вашего приложения.
Пространства имен (Namespaces)
Теперь Livewire поставляется с определенным мнением о структуре приложения. По умолчанию вам доступны два пространства имен: pages:: для компонентов-страниц и layouts:: для макетов — всё остальное размещается в resources/views/components рядом с вашими Blade-компонентами.
Route::livewire('/dashboard', 'pages::dashboard');
Для модульных приложений вы можете регистрировать собственные пространства имен. Группируйте админские компоненты под admin::, платежные под billing:: или любым другим способом, который имеет смысл для вашей архитектуры.
Скрипты и стили
JavaScript и CSS компонентов теперь располагаются рядом с самими компонентами. Добавляйте теги <script> и <style> напрямую в ваш шаблон:
<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>
Стили автоматически изолируются (scoped) для вашего компонента — ваш класс .title не будет «протекать» в другие части страницы. Нужны глобальные стили? Добавьте атрибут global: <style global>.
Скрипты имеют доступ к this для контекста компонента — это псевдоним для привычного вам $wire.
И то, и другое отправляется в браузер как нативные файлы .js/.css, которые автоматически кешируются для оптимальной производительности.
Изолированные области (Islands)
Islands (островки) — это главная особенность Livewire 4. Они позволяют создавать изолированные области внутри компонента, которые обновляются независимо:
<div>
@island
<div>
Выручка: {{ $this->revenue }}
<button wire:click="$refresh">Обновить</button>
</div>
@endisland
<div>
<!-- Это не будет перерисовываться при обновлении "островка" -->
Другой контент...
</div>
</div>
При клике на «Обновить» перерисовывается только «островок». Остальное остается нетронутым. Раньше для достижения подобного уровня изоляции вам пришлось бы выносить это в отдельный дочерний компонент со всей сопутствующей нагрузкой в виде пропсов и событий.
Преимущества для производительности глубже, чем просто обновление DOM. Если комбинировать «островки» с вычисляемыми свойствами (computed properties), то загружаются только данные, необходимые этому конкретному «островку». Если ваш компонент имеет три «островка», каждый из которых ссылается на разные вычисляемые свойства, то обновление одного «островка» запускает только его запросы. Вы изолируете нагрузку от базы данных и до самого отрисованного HTML.
«Islands» поддерживают:
- Ленивую загрузку (
lazy: true) - Именование для таргетинга между компонентами (
name: 'revenue') - Добавление контента для бесконечной прокрутки:
<button wire:click="loadMore" wire:island.append="feed">
Загрузить еще
</button>
Слоты и передача атрибутов
Если вы использовали слоты и передачу атрибутов в Blade-компонентах, вы почувствуете себя как дома.
Слоты позволяют родительским компонентам вставлять контент в дочерние, сохраняя всё реактивным:
<livewire:card :$post>
<h2>{{ $post->title }}</h2>
<button wire:click="delete({{ $post->id }})">Удалить</button>
</livewire:card>
Содержимое слота вычисляется в контексте родителя, поэтому wire:click="delete" вызывает метод родителя.
Передача атрибутов (Attribute forwarding) позволяет передавать HTML-атрибуты сквозным образом:
<!-- В родительском компоненте -->
<livewire:post.show :$post class="mt-4" />
<!-- Внутри компонента post.show -->
<div {{ $attributes }}>
...
</div>
Перетаскивание (Drag and drop)
Сортировка перетаскиванием без необходимости во внешних библиотеках:
<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.
Плавные переходы (Smooth transitions)
Директива wire:transition добавляет аппаратно-ускоренные анимации с использованием View Transitions API браузера:
@if ($showAlertMessage)
<div wire:transition>
<!-- Сообщение плавно появляется/исчезает -->
</div>
@endif
Для пошаговых мастеров (wizards) или каруселей, где важен направление, можно указывать типы переходов:
#[Transition(type: 'forward')]
public function next() { $this->step++; }
#[Transition(type: 'backward')]
public function previous() { $this->step--; }
Затем можно настраивать CSS-анимации для каждого направления, используя псевдоэлементы ::view-transition-old() и ::view-transition-new().
Оптимистичный UI (Optimistic UI)
Сделайте ваш интерфейс мгновенно отзывчивым. Эти директивы обновляют страницу сразу — без необходимости дожидаться ответа от сервера.
wire:showпереключает видимость с помощью CSS (без удаления из DOM, без сетевого запроса):
<div wire:show="showModal">
<!-- Скрывается/показывается мгновенно -->
</div>
wire:text мгновенно обновляет текстовое содержимое:
Лайков: <span wire:text="likes"></span>
wire:bind реактивно связывает любой HTML-атрибут:
<input wire:model="message" wire:bind:class="message.length > 240 && 'text-red-500'">
$dirty отслеживает несохраненные изменения:
<div wire:show="$dirty">У вас есть несохраненные изменения</div>
<div wire:show="$dirty('title')">Заголовок изменен</div>
Состояния загрузки (Loading states)
В дополнение к существующей wire:loading из v3, Livewire 4 автоматически добавляет атрибут data-loading к любому элементу, который инициирует сетевой запрос.
Это упрощает стилизацию состояний загрузки напрямую из CSS и таргетинг на соседние, родительские или дочерние элементы:
<button wire:click="save" class="data-loading:opacity-50">
Сохранить <svg class="not-in-data-loading:hidden">...</svg>
</button>
Встроенные плейсхолдеры (Inline placeholders)
Для ленивых (lazy) компонентов и «островков» директива @placeholder позволяет определять состояния загрузки прямо рядом с контентом, который они заменяют:
@placeholder
<div class="animate-pulse h-32 bg-gray-200 rounded"></div>
@endplaceholder
<div>
<!-- Фактический контент загружается здесь -->
</div>
Никаких отдельных представлений-плейсхолдеров или методов — ваш скелетон находится внутри вашего компонента.
Мощные JavaScript-инструменты
Когда вам нужно перейти на уровень JavaScript, Livewire 4 готов к этому.
wire:refдает элементам имена, по которым можно к ним обращаться:
<livewire:modal wire:ref="modal" />
$this->dispatch('close')->to(ref: 'modal');
Также можно обращаться к рефам из скриптов компонента:
<input wire:ref="search" type="text" />
<script>
this.$refs.search.addEventListener('keydown', (e) => {
// Обрабатываем события клавиатуры...
})
</script>
Методы с #[Json] возвращают данные напрямую в JavaScript:
#[Json]
public function search($query)
{
return Post::where('title', 'like', "%{$query}%")->get();
}
<script>
let results = await this.search('livewire')
console.log(results)
</script>
$js-действия выполняются только на стороне клиента:
<button wire:click="$js.bookmark">В закладки</button>
<script>
this.$js.bookmark = () => {
this.bookmarked = !this.bookmarked
this.save()
}
</script>
Перехватчики (Interceptors) подключаются к запросам на любом уровне:
<script>
this.intercept('save', ({ onSuccess, onError }) => {
onSuccess(() => showToast('Сохранено!'))
onError(() => showToast('Не удалось сохранить', 'error'))
})
</script>
Используйте глобальные перехватчики для общесистемных задач, таких как истечение сессии:
Livewire.interceptRequest(({ onError }) => {
onError(({ response, preventDefault }) => {
if (response.status === 419) {
preventDefault()
if (confirm('Сессия истекла. Обновить страницу?')) {
window.location.reload()
}
}
})
})
Обновление (Upgrading)
Livewire 4 сохраняет сильную обратную совместимость. Ваши существующие компоненты продолжат работать — новый однофайловый формат используется по умолчанию для новых компонентов, но компоненты на основе классов по-прежнему полностью поддерживаются.
Ознакомьтесь с руководством по обновлению →
Если вы хотите увидеть всё это в действии, я записал новую серию на Laracasts, подробно освещающую каждую функцию на реальных примерах. Смотрите серию по Livewire 4 →
Livewire 4 уже доступен:
composer require livewire/livewire:^4.0