Livewire 4 нарешті тут — і це найбільший реліз.
Йдеться не про ускладнення — а про кращі значення за замовчуванням, менше перешкод та потужніші інструменти для створення саме того, що ви хочете. Ми місяцями переосмислювали, якими мають бути Livewire-компоненти, і пишаємося результатом.
Поглянемо.
Найпомітніша зміна в 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 (optional)
├── counter.js (optional)
└── counter.test.php (optional)
Створити такий можна з --mfc, а конвертувати формати — командою php artisan livewire:convert.
Посилайтеся на компоненти однаково всюди. Livewire 4 вводить Route::livewire():
// Before (v3) - Still supported
Route::get('/posts/create', CreatePost::class);
// After (v4)
Route::livewire('/posts/create', 'pages::post.create');
Новий синтаксис звертається до компонентів за іменем, а не за класом. Це узгоджується зі способом рендерингу компонентів у вашому додатку.
Livewire тепер пропонує рекомендації щодо структури додатку. За замовчуванням доступні два простори імен: pages:: для сторінкових компонентів і layouts:: для layout'ів — усе інше розміщується в 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 — головна фіча Livewire 4. Вони дозволяють створювати ізольовані області всередині компонента, які оновлюються незалежно одна від одної:
<div>
@island
<div>
Revenue: {{ $this->revenue }}
<button wire:click="$refresh">Refresh</button>
</div>
@endisland
<div>
<!-- This won't re-render when the island updates -->
Other content...
</div>
</div>
При натисканні «Refresh» перевідренерується лише island — решта залишиться незмінною. Раніше для подібної ізоляції доводилося виносити частину в дочірній компонент і працювати з props та events.
Переваги продуктивності не обмежуються лише DOM-оновленнями. У поєднанні з computed properties підвантажуються лише ті дані, які потрібні конкретному island. Якщо компонент має три islands, кожен із різними computed, оновлення одного запускає тільки відповідні запити — ви ізолюєте навантаження від бази даних аж до відрендереного HTML.
Islands підтримують lazy loading (lazy: true), іменування для таргетингу між компонентами (name: 'revenue') і додавання контенту для infinite scroll:
<button wire:click="loadMore" wire:island.append="feed">
Load more
</button>
Якщо ви використовували slots і передачу атрибутів у Blade-компонентах, почуваєтеся як удома.
Slots дозволяють батькам вставляти вміст у дітей, зберігаючи реактивність:
<livewire:card :$post>
<h2>{{ $post->title }}</h2>
<button wire:click="delete({{ $post->id }})">Delete</button>
</livewire:card>
Вміст слота оцінюється в контексті батьківського компонента, тож wire:click="delete" викликає метод батька.
Передача атрибутів дозволяє прокидати HTML-атрибути далі:
<livewire:post.show :$post class="mt-4" />
<!-- Inside post.show component -->
<div {{ $attributes }}>
...
</div>
Сортування перетягуванням без зовнішніх бібліотек:
<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 is the ID, $position is the new index
}
Плавні анімації працюють автоматично. Додайте drag handles з wire:sort:handle, забороніть перетягування для інтерактивних елементів через wire:sort:ignore, і перетягуйте між кількома списками з wire:sort:group.
Директива wire:transition додає апаратно-прискорені анімації через View Transitions API:
@if ($showAlertMessage)
<div wire:transition>
<!-- Message smoothly fades in/out -->
</div>
@endif
Для покрокових майстрів або каруселей, де важливий напрям, можна вказати типи переходів:
#[Transition(type: 'forward')]
public function next() { $this->step++; }
#[Transition(type: 'backward')]
public function previous() { $this->step--; }
Потім налаштуйте CSS-анімації для кожного напрямку за допомогою псевдоелементів ::view-transition-old() і ::view-transition-new().
Робіть інтерфейс миттєвим. Ці директиви оновлюють сторінку негайно — без очікування відповіді сервера.
wire:show перемикає видимість через CSS (без видалення з DOM і без мережевого запиту):
<div wire:show="showModal">
<!-- Hidden/shown instantly -->
</div>
wire:text миттєво оновлює текст:
Likes: <span wire:text="likes"></span>
wire:bind реактивно зв'язує будь-який HTML-атрибут:
$dirty відслідковує незбережені зміни:
<div wire:show="$dirty">You have unsaved changes</div>
<div wire:show="$dirty('title')">Title modified</div>
Окрім існуючого wire:loading з v3, Livewire 4 автоматично додає атрибут data-loading до будь-якого елементу, що ініціює мережевий запит.
Це спрощує стилізацію станів завантаження через CSS і дає змогу таргетити сусідні, батьківські або дочірні елементи:
<button wire:click="save" class="data-loading:opacity-50">
Save <svg class="not-in-data-loading:hidden">...</svg>
</button>
Для lazy-компонентів і islands директива @placeholder дає можливість визначити стан завантаження поряд із вмістом, який вона замінює:
@placeholder
<div class="animate-pulse h-32 bg-gray-200 rounded"></div>
@endplaceholder
<div>
<!-- Actual content loads here -->
</div>
Ніяких окремих view або методів для плейсхолдерів — ваш скелетон живе в компоненті.
Коли треба опуститися до JavaScript, Livewire 4 має потрібні інструменти.
wire:ref дає елементам імена для таргетингу:
<livewire:modal wire:ref="modal" />
$this->dispatch('close')->to(ref: 'modal');
До refs також можна звертатися з скриптів компонента:
<input wire:ref="search" type="text" />
<script>
this.$refs.search.addEventListener('keydown', (e) => {
// Handle keyboard events...
})
</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">Bookmark</button>
<script>
this.$js.bookmark = () => {
this.bookmarked = !this.bookmarked
this.save()
}
</script>
Interceptors дають хук у запити на будь-якому рівні:
<script>
this.intercept('save', ({ onSuccess, onError }) => {
onSuccess(() => showToast('Saved!'))
onError(() => showToast('Failed to save', 'error'))
})
</script>
Використовуйте глобальні інтерсептори для загальнододаткових сценаріїв, наприклад логіки закінчення сесії:
Livewire.interceptRequest(({ onError }) => {
onError(({ response, preventDefault }) => {
if (response.status === 419) {
preventDefault()
if (confirm('Session expired. Refresh?')) {
window.location.reload()
}
}
})
})
Livewire 4 зберігає високу зворотну сумісність. Ваші існуючі компоненти працюватимуть — новий single-file формат стає стандартом для нових компонентів, але класові компоненти залишаються повністю підтримуваними.
Дивіться посібник з оновлення →
Щоб побачити все в дії, я записав нову серію на Laracasts з детальними прикладами з реального життя. Дивитися серію Livewire 4 →
Livewire 4 вже доступний:
composer require livewire/livewire:^4.0
Для повної документації відвідайте livewire.laravel.com.
Ви хочете навчитися, як інтегрувати Google OAuth у вашому проекті Laravel, використовуючи Socialite? Дізнайтеся, як налаштувати доступ до сервісів Google, таких як Календар, у нашій сьогоднішній статті
Зазирніть у світ Laravel, де потужний CLI-фреймворк відкриває нові можливості для розробки командного інтерфейсу. Дізнайтеся, як створити просту утиліту для перевірки акцій, яка працює з Docker, та які переваги це може принести у вашому проєкті!
Хочете забезпечити повну прозорість у своїх Laravel-додатках? Пакет Laravel Audit Log допоможе вам детально відстежувати всі зміни моделей Eloquent та відповідати вимогам регуляторів. Читайте далі, щоб дізнатися, як цей потужний інструмент може підвищити надійність вашого проєкту