1. בקצרה
Livewire 4 היא גרסה גדולה עם מסלול שדרוג קטן. מודל הקומפוננטות לא השתנה: מחלקת PHP, תצוגת Blade, state בשרת, HTML על גבי הרשת. מה שהשתנה הוא היכן הקומפוננטות נמצאות, איך הן נכתבות, וכמה מהעמוד עדכון בודד נוגע בו.
מה מרוויחים
- קומפוננטות בקובץ יחיד — מחלקה ו-markup באותו קובץ.
- Islands — אזורים שמתרנדרים מחדש באופן עצמאי, בלי לפצל לקומפוננטות ילד.
- Slots — ההורה מזריק markup לילדים, והוא מוערך בהקשר של ההורה.
- Optimistic UI —
wire:show,wire:text,wire:bindמעדכנים את ה-DOM בלי סבב מול השרת. - גרירה ושחרור —
wire:sort, בלי ספרייה חיצונית. - Scoped CSS — סגנונות קומפוננטה שלא יכולים לדלוף החוצה.
מה ינשוך אתכם
- תגיות
<livewire:…>לא סגורות לא מרנדרות דבר, בשקט. - המשמעות של מודיפיירים ב-
wire:modelהשתנתה — שדות מפסיקים להסתנכרן. - נקודת הקצה של העדכונים עברה ל-
/livewire-{hash}/— כללי תשתית נשברים. - המודיפיירים של
wire:transitionהוסרו. - Volt שולב בליבה.
כל אחד מאלה מוסבר בפירוט בהמשך, עם קוד לפני ואחרי.
2. זה לצד זה
| תחום | Livewire 3 | Livewire 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 |
| Slots | לא נתמך | {{ $slot }}, <wire:slot name="…"> |
| גרירה ושחרור | SortableJS וקוד חיבור | wire:sort |
| UI מיידי | Alpine.js ידני | wire:show, wire:text, wire:bind |
| CSS של קומפוננטה | גיליון סגנונות גלובלי | <style> מתוחם בתוך הקומפוננטה |
| פעולות מקבילות | לא זמין | #[Async], wire:click.async |
| נקודת קצה לעדכונים | /livewire/update |
/livewire-{hash}/update |
| JS hooks | Livewire.hook('request'|'commit') |
interceptRequest(), interceptMessage() |
| Volt | חבילה נפרדת | שולב בליבה |
| בדיקות | PHPUnit או Pest, tests/Feature/Livewire |
מומלץ Pest, הבדיקות יכולות לשבת ליד הקומפוננטה |
3. השדרוג, צעד אחר צעד
# 1. העלאת גרסת התלות
composer require livewire/livewire:^4.0
# 2. ניקוי כל המטמונים — תצוגות מקומפלות ישנות הן הכשל הראשון הנפוץ
php artisan optimize:clear
# 3. הריצו את חבילת הבדיקות לפני שנוגעים בכל דבר אחר
php artisan testLaravel Shift יכול לבצע אוטומטית חלק גדול מהעבודה המכנית. בין אם משתמשים בו ובין אם לא, עברו אחר כך על הרשימה שלהלן — הכשלים השקטים הם בדיוק אלה שכלי אוטומטי הכי פחות סביר שיתריע עליהם.
סדר עבודה שבדרך כלל עובר חלק
- שדרגו את החבילה ונקו מטמונים.
- תקנו את שינויי השמות בקונפיגורציה (פרק 4).
- תקנו את הניתוב (פרק 5).
- חפשו בקוד תגיות קומפוננטה לא סגורות (פרק 6).
- בדקו כל
wire:modelעם מודיפייר (פרק 7). - עדכנו את כללי התשתית לנקודת הקצה החדשה (פרק 8).
- החליפו מודיפיירים של
wire:transitionשהוסרו (פרק 9). - העבירו קומפוננטות Volt, אם אתם משתמשים ב-Volt (פרק 12).
- הריצו שוב את הבדיקות, ואז עברו על האפליקציה בדפדפן.
- רק אז התחילו לאמץ islands, slots וקומפוננטות בקובץ יחיד.
4. שינויי שמות בקונפיגורציה
| Livewire 3 | Livewire 4 | הערה |
|---|---|---|
layout | component_layout | משתמש כעת ב-namespace layouts:: |
lazy_placeholder | component_placeholder | — |
smart_wire_keys | smart_wire_keys | ברירת המחדל התהפכה מ-false ל-true |
| — | component_locations | תיקיות שנסרקות לאיתור קומפוננטות |
| — | component_namespaces | שורשים בעלי שם, כמו pages:: |
| — | make_command | יצירה בקובץ יחיד או מבוססת מחלקה |
| — | csp_safe | Build שתואם ל-CSP מחמיר יותר |
'layout' => 'layouts.app',
'lazy_placeholder' => 'livewire.placeholder','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. ניתוב
לקומפוננטות עמוד מלא יש מאקרו ניתוב ייעודי.
use App\Livewire\Dashboard;
Route::get('/dashboard', Dashboard::class)
->middleware('auth')
->name('dashboard');use App\Livewire\Dashboard;
// לפי מחלקה
Route::livewire('/dashboard', Dashboard::class)
->middleware('auth')
->name('dashboard');
// או לפי שם קומפוננטה, עבור קומפוננטות מבוססות view
Route::livewire('/dashboard', 'pages::dashboard')
->middleware('auth')
->name('dashboard');grep -rn "Route::get(.*::class" routes/, ואז לבדוק אילו
מהמחלקות האלה יורשות מ-Livewire\Component.
6. תגיות קומפוננטה חייבות להיסגר
<livewire:user-profile>
<livewire:order-list><livewire:user-profile />
<livewire:order-list />איך למצוא אותן
# תגיות קומפוננטה שאינן סגורות עצמית
grep -rn "<livewire:[^>]*[^/]>" resources/views/
# צורת הדירקטיבה @livewire() לא מושפעת
grep -rn "@livewire(" resources/views/ברגע שהאפליקציה רצה, שווה להשקיע במעבר ידני על התהליכים המרכזיים: בלוק חסר קל לפספס ב-diff ובולט מאוד על המסך.
7. wire:model השתנה פעמיים
שני שינויים בלתי תלויים, ושניהם יוצרים שדה ש"פשוט מפסיק לעבוד" במקום
שגיאה. בדקו כל wire:model שיש עליו מודיפייר.
7.1 מודיפיירים שולטים כעת בסנכרון בצד הלקוח
ב-v3, .blur ו-.change קבעו מתי נשלחת בקשת רשת. הערך
עצמו תמיד נעקב בצד הלקוח. ב-v4 הם קובעים מתי הערך מסתנכרן בכלל.
הוסיפו .live כדי לשחזר את ההתנהגות הישנה.
<input wire:model.blur="title">
<select wire:model.change="status"><input wire:model.live.blur="title">
<select wire:model.live.change="status">7.2 אין יותר bubbling מאלמנטים ילדים
wire:model על אלמנט עוטף כבר לא תופס אירועים שמבעבעים מ-inputs מקוננים.
<div wire:model="value">
<input type="text">
</div><div wire:model.deep="value">
<input type="text">
</div>איך למצוא אותם
# כל wire:model עם מודיפייר
grep -rn "wire:model\.[a-z]" resources/views/
# wire:model על אלמנט שאינו input הוא מועמד ל-bubbling
grep -rn "<div[^>]*wire:model" resources/views/8. נקודת הקצה של העדכונים עברה
כל כתובות ה-URL של Livewire כוללות כעת hash: /livewire/ הפך ל-
/livewire-{hash}/.
/livewire/.
מקומות לבדוק
- בלוקי
locationשל nginx או Apache - רשימות היתרים של WAF ו-firewall
- כללי עקיפת מטמון ב-CDN (Cloudflare page rules וכדומה)
- מגבילי קצב שמבוססים על הנתיב
- כללי ניתוב ב-load balancer
- ניטור ובדיקות uptime שמכוונים לנקודת הקצה
- רשומות CSP
connect-srcשמקובעות לנתיב
location /livewire/ {
# …
}location ~ ^/livewire(-[a-z0-9]+)?/ {
# …
}9. wire:transition נבנה מחדש
wire:transition כבר לא מקבל מודיפיירים כמו .opacity,
.scale או .duration.200ms. כעת הוא בנוי על View Transitions API
המובנה בדפדפן, והאנימציה מוגדרת ב-CSS.
<div wire:transition.opacity.duration.300ms>
…
</div><div wire:transition>
…
</div>::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 300ms;
}10. שינויים ב-API של PHP
Streaming
$this->stream(to: '#container', content: 'Hello', replace: true);$this->stream('Hello', replace: true, el: '#container');טעינת קומפוננטה (mounting)
mount($name, $params = [], $key = null)mount($name, $params = [], $key = null, $slots = [])רלוונטי אם אתם טוענים קומפוננטות באופן תכנותי או שיש לכם קוד מותאם שעוטף את מנגנון ה-mounting של Livewire. קוד אפליקציה רגיל לא מושפע.
attributes חדשים שכדאי להכיר
| Attribute | מטרה |
|---|---|
#[Async] | הרצת action במקביל, מחוץ לתור הבקשות |
#[Renderless] | דילוג על הרינדור מחדש אחרי action |
#[Prop] | הגדרת property כ-prop שמגיע מההורה |
#[Json] | החזרת נתונים ישירות ל-JavaScript |
11. שינויים ב-API של JavaScript
פעולות $js
$wire.$js('showToast', { message: 'Saved' });$wire.$js.showToast = { message: 'Saved' };Hooks הפכו ל-interceptors
ה-hooks commit ו-request הוצאו משימוש (deprecated) לטובת
interceptMessage() ו-interceptRequest().
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();
}
});
});
});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 updated', component.name);
});
});
});magic properties חדשים
{{-- שגיאות ולידציה, בצד הלקוח --}}
<div x-show="$errors.has('email')" x-text="$errors.first('email')"></div>12. Volt שולב בליבה
התחביר של Volt לקובץ יחיד היה אב הטיפוס של מה שהפך לקומפוננטות בקובץ יחיד של Livewire 4. החבילה הנפרדת כבר לא נחוצה.
| Volt | Livewire 4 |
|---|---|
Livewire\Volt\Component | Livewire\Component |
Volt::route() | Route::livewire() |
Volt::test() | Livewire::test() |
| Service provider של Volt | הסירו אותו |
החבילה livewire/volt | הסירו את התלות |
composer remove livewire/volt<?php
use Livewire\Volt\Component;
new class extends Component {
public string $title = '';
};
?>
<div>…</div><?php
use Livewire\Component;
new class extends Component {
public string $title = '';
};
?>
<div>…</div>13. אימוץ היכולות החדשות
ברגע שהאפליקציה ירוקה על v4, אלה השינויים שמחזירים את ההשקעה הכי מהר, בערך לפי סדר התועלת ביחס למאמץ.
1. Islands, בעמוד האיטי ביותר שלכם
מצאו את העמוד שבו אינטראקציה קטנה אחת מריצה מחדש הרבה שאילתות — בדרך כלל דשבורד או טבלה
עם פילטרים. עטפו את האזורים העצמאיים ב-@island.
<div>
<div class="card">Revenue: {{ $this->revenue }}</div>
<div class="card">Pending jobs: {{ $this->queue }}</div>
<div class="card">Latest activity: …</div>
</div><div>
@island(name: 'revenue')
<div class="card">Revenue: {{ $this->revenue }}</div>
@endisland
@island(name: 'queue', poll: '5s')
<div class="card">Pending jobs: {{ $this->queue }}</div>
@endisland
@island(name: 'activity', lazy: true)
@placeholder
<div class="card skeleton animate-pulse h-48"></div>
@endplaceholder
<div class="card">Latest activity: …</div>
@endisland
</div>2. החליפו סבבים מול השרת בדירקטיבות צד לקוח
<button wire:click="$toggle('showFilters')">Filters</button>
@if ($showFilters)
<div class="filters">…</div>
@endif<button wire:click="$toggle('showFilters')">Filters</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 הפתרון הסטנדרטי ל"זה מתרנדר מחדש יותר מדי" היה קומפוננטת ילד. אם לילד אין state משלו והוא לא נמצא בשימוש חוזר, island בתוך ההורה קל יותר — קומפוננטה אחת, מחלקה אחת, snapshot אחד.
5. השתמשו ב-#[Renderless] לעבודה בלתי נראית
use Livewire\Attributes\Renderless;
#[Renderless]
public function trackView(): void
{
$this->post->increment('views');
}6. המירו קומפוננטות לקובץ יחיד — אחרונות, ורק היכן שזה עוזר
זה שינוי קוסמטי. עשו זאת בהזדמנות, כשאתם נוגעים בקבצים, ולא כספרינט מיגרציה. קומפוננטות קטנות מרוויחות; קומפוננטות עם לוגיקה כבדה ותלויות רבות ברורות לעיתים קרובות יותר כמחלקות.
14. רשימת בדיקה לשדרוג
לפני שמתחילים
- חבילת הבדיקות עוברת על Livewire 3.
- ענף (branch), וגיבוי של מסד הנתונים אם הסביבה משותפת.
- מישהו זמין לעבור על האפליקציה אחר כך — הכשלים השקטים דורשים עיניים.
שינויים מכניים
composer require livewire/livewire:^4.0php artisan optimize:clearlayout→component_layout(וה-namespacelayouts::)lazy_placeholder→component_placeholderRoute::get(…, Component::class)→Route::livewire(…)- כל תגית
<livewire:…>סגורה עצמית wire:model.blur/.change→ הוסיפו.livewire:modelעל עוטף → הוסיפו.deepwire:scroll→wire: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 ו-firewall עודכנו
- מגבילי קצב ובדיקות ניטור הופנו מחדש
- CSP
connect-srcנבדק
אימות
- חבילת הבדיקות ירוקה
- כל טופס נשלח ועובר ולידציה
- העלאות קבצים עובדות, כולל מחוון ההתקדמות
- טבלאות מתחלקות לעמודים, ממוינות ומסוננות
- חלונות מודאליים נפתחים ונסגרים
- ווידג'טים של צד שלישי בתוך
wire:ignoreעדיין מאותחלים - קונסולת הדפדפן נקייה בתהליכים המרכזיים
- בקשות עדכון מחזירות 200 בפרודקשן, ולא 403 מ-WAF
משאבים רשמיים
- מדריך השדרוג הרשמי: livewire.laravel.com/docs/upgrading
- התיעוד של Livewire: livewire.laravel.com/docs
אם יש סתירה בין התיעוד הזה למקור הרשמי, המקור הרשמי צודק. מצאתם שגיאה? ספרו לנו.