[ Livewire · Dokumentation ]

Livewire 4 Dokumentation

Die vollständige Dokumentation zu Livewire 4: Single-File-Komponenten, Islands mit eigenem Rendering, Slots, eingebautes Drag and Drop und clientseitige Direktiven — mit Hinweisen, wo sich Version 4 von Version 3 unterscheidet.

  • 4.x · Aktuell
  • Laravel 11 / 12
  • PHP 8.2+
  • 26 Kapitel
  • 41 Min. Lesezeit

1. Was Livewire 4 ändert

Livewire 4 ist das größte Release in der Geschichte des Frameworks. Das Komponentenmodell selbst ist gleich geblieben — eine PHP-Klasse plus eine Blade-Vorlage, Zustand auf dem Server, HTML über die Leitung —, doch fast alles drumherum hat sich bewegt: wo Komponenten liegen, wie sie geschrieben werden und wie viel einer Seite ein einzelnes Update berührt.

Die fünf wichtigsten Punkte

NeuerungWas sie bringt
Single-File-Komponenten Klasse und Markup liegen in einer .blade.php-Datei. Kein Springen mehr zwischen zwei Verzeichnissen für eine Komponente mit zwanzig Zeilen.
Islands Isolierte Bereiche innerhalb einer Komponente, die sich eigenständig neu rendern. Ein tickender Umsatzzähler führt nicht länger die Abfragen des gesamten Dashboards erneut aus.
Slots Eltern reichen Markup in Kinder hinein — ausgewertet im Kontext des Elternteils, sodass wire:click in einem Slot die Methode des Elternteils aufruft.
Optimistisches UI wire:show, wire:text, wire:bind ändern das DOM sofort auf dem Client, ohne Roundtrip zum Server.
Drag and Drop wire:sort ist eingebaut. Kein SortableJS, kein Klebecode.

Ist das ein Neuschreiben der Anwendung?

Nein. Livewire 4 bleibt weitgehend abwärtskompatibel: klassenbasierte Komponenten funktionieren weiter. Single-File ist der Standard für neue Komponenten, keine erzwungene Migration. Es gibt eine Reihe von Breaking Changes, die Sie vor dem Upgrade lesen sollten — sie sind im Vergleich Livewire 3 und 4 gesammelt, und der offizielle Upgrade Guide bleibt die maßgebliche Liste.

Sie kommen von Livewire 3? Das eigentlich Neue steckt in den Kapiteln 3, 6, 10 und 11 — Single-File-Komponenten, die neue wire:model-Semantik, Islands und Slots. Der Rest des Komponentenmodells wird Ihnen vertraut vorkommen.

2. Installation und Voraussetzungen

Bash
composer require livewire/livewire:^4.0

php artisan optimize:clear

Wie in Version 3 werden die Assets automatisch eingebunden. @livewireStyles und @livewireScripts müssen Sie nur bei einem abweichenden Layout oder einer strengen Content Security Policy von Hand setzen.

Die Livewire-Endpunkte haben sich geändert. Update-URLs enthalten jetzt einen Hash: aus /livewire/ wurde /livewire-{hash}/. Firewall-Regeln, WAF-Ausnahmen, CDN-Bypass-Regeln oder nginx-Locations, die auf den wörtlichen Pfad /livewire/ festgelegt sind, müssen auf das neue Muster erweitert werden — sonst wirkt die Anwendung in der Produktion kaputt, während sie auf Ihrem Laptop einwandfrei läuft.

Konfiguration veröffentlichen

Bash
php artisan livewire:publish --config

Einstellungen, die in Version 4 neu sind oder umbenannt wurden:

EinstellungZweck
component_locationsVerzeichnisse, die nach Komponenten durchsucht werden. Standard: resources/views/components und resources/views/livewire.
component_namespacesBenannte Wurzeln für Komponenten, etwa pages::.
component_layoutHieß in v3 layout. Nutzt den Namespace layouts::.
component_placeholderHieß in v3 lazy_placeholder.
make_commandLegt fest, was make:livewire erzeugt: Single-File oder klassenbasiert.
smart_wire_keysJetzt standardmäßig true.
csp_safeErzeugt einen Build, der strengere Content Security Policies erfüllt.

Den Standard-Komponentenstil wählen

config/livewire.php
// Weiterhin klassenbasierte statt Single-File-Komponenten erzeugen
'make_command' => [
    'type' => 'class',
],

3. Single-File-Komponenten

Die auffälligste Änderung im Arbeitsalltag. Eine Komponente ist jetzt eine Blade-Datei, die mit einem PHP-Block samt anonymer Klasse beginnt, gefolgt vom Markup.

Bash
php artisan make:livewire post.create
# resources/views/components/post/⚡create.blade.php
resources/views/components/post/⚡create.blade.php
<?php

use Livewire\Component;
use App\Models\Post;
use Livewire\Attributes\Validate;

new class extends Component {
    #[Validate('required|string|min:5|max:180')]
    public string $title = '';

    #[Validate('required|string|min:50')]
    public string $body = '';

    public function save()
    {
        $this->validate();

        Post::create([
            'title' => $this->title,
            'body'  => $this->body,
            'user_id' => auth()->id(),
        ]);

        $this->reset();

        $this->dispatch('notify', message: 'Post created');
    }
};

?>

<div>
    <form wire:submit="save">
        <input type="text" wire:model="title" placeholder="Title">
        @error('title') <span class="error">{{ $message }}</span> @enderror

        <textarea wire:model="body" placeholder="Body"></textarea>
        @error('body') <span class="error">{{ $message }}</span> @enderror

        <button type="submit">Save post</button>
    </form>
</div>

Öffentliche Eigenschaften stehen im Markup als gewöhnliche Variablen bereit — {{ $title }} funktioniert genauso wie zuvor in einer separaten Vorlagendatei.

Zu dem Emoji

Der Standarddateiname trägt das Präfix ⚡. Es ist eine optische Markierung, die Livewire-Komponenten von gewöhnlichen Blade-Komponenten im selben Verzeichnis abhebt, und sie ist optional — Sie können sie in config/livewire.php abschalten, wenn Ihre Toolchain, Ihr Terminal oder Ihr Team schlichte Namen bevorzugt.

Multi-File-Komponenten

Wird eine Komponente für eine einzelne Datei zu groß, erzeugen Sie sie als Multi-File-Komponente und erhalten Klasse, Vorlage und Test wieder getrennt.

Bash
php artisan make:livewire post.create --mfc

Klassenbasierte Komponenten funktionieren weiter

app/Livewire/CreatePost.php
<?php

namespace App\Livewire;

use Livewire\Component;

class CreatePost extends Component
{
    public string $title = '';

    public function render()
    {
        return view('livewire.create-post');
    }
}
Was wählen. Single-File gewinnt bei kleinen und mittleren Komponenten — ein Formular, eine Tabellenzeile, ein Widget. Greifen Sie zu klassenbasiert oder Multi-File, wenn die Klasse echte Logik trägt, viele Abhängigkeiten hat oder Ihr Team bereits Konventionen rund um app/Livewire etabliert hat.

4. Ablage, Benennung und Namespaces

Livewire-Komponenten liegen standardmäßig nicht mehr in einem eigenen Silo resources/views/livewire, sondern direkt neben Ihren anderen Blade-Komponenten.

Verzeichnisstruktur
resources/views/
├── components/
│   ├── ⚡counter.blade.php              → <livewire:counter />
│   ├── post/
│   │   ├── ⚡create.blade.php           → <livewire:post.create />
│   │   └── ⚡index.blade.php            → <livewire:post.index />
│   └── button.blade.php                 (a plain Blade component)
└── pages/
    └── ⚡dashboard.blade.php            → <livewire:pages::dashboard />

Rendern

Blade
{{-- Nach Name --}}
<livewire:counter />

{{-- Verschachtelte Verzeichnisse mit Punkten --}}
<livewire:post.create />

{{-- Mit Namespace --}}
<livewire:pages::post.create />

{{-- Mit Props --}}
<livewire:post.create :title="$initialTitle" :author="$user" />
Komponenten-Tags müssen geschlossen werden. In v4 rendert ein nicht geschlossenes <livewire:some-component> schlicht gar nichts — keine Fehlermeldung, keine Ausgabe. Immer selbstschließend notieren: <livewire:some-component />. Das ist die häufigste Überraschung beim Übernehmen von Blade-Dateien aus v3.

Routing auf eine Komponente

Ganzseitige Komponenten erhalten ein eigenes Routen-Makro. Die alte Form Route::get('/dashboard', Dashboard::class) wird ersetzt durch:

routes/web.php
use App\Livewire\Dashboard;

// Nach Klasse
Route::livewire('/dashboard', Dashboard::class);

// Nach Komponentenname — die übliche Wahl für vorlagenbasierte Komponenten
Route::livewire('/dashboard', 'pages::dashboard')
    ->middleware('auth')
    ->name('dashboard');

Layouts

config/livewire.php
'component_layout' => 'layouts::app',
PHP
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;

new class extends Component {
    #[Layout('layouts::app')]
    #[Title('Dashboard')]
    public function render() { /* … */ }
};

5. Eigenschaften und Props

Öffentliche Eigenschaften sind weiterhin der Zustand der Komponente, werden weiterhin zwischen Anfragen in den Snapshot serialisiert und unterliegen denselben Typbeschränkungen: Skalare, Arrays aus Skalaren, Eloquent-Modelle und -Collections, Carbon, Enums.

PHP
new class extends Component {
    public string $name = '';
    public ?int $age = null;
    public array $tags = [];
    public bool $isPublic = false;
};

Props vom Elternteil: #[Prop]

Werte, die aus einer Eltern-Vorlage übergeben werden, lassen sich explizit deklarieren. So ist die öffentliche Schnittstelle der Komponente auf einen Blick lesbar, und „kommt von außen“ ist sauber von „ist interner Zustand“ getrennt.

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

use Livewire\Component;
use Livewire\Attributes\Prop;

new class extends Component {
    #[Prop]
    public string $type = 'info';

    #[Prop]
    public bool $dismissible = false;
};

?>

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $slot }}

    @if ($dismissible)
        <button wire:click="$dispatch('dismiss')">×</button>
    @endif
</div>
Blade
<livewire:alert type="warning" dismissible class="mt-4">
    The invoice is overdue.
</livewire:alert>

mount() läuft weiterhin zuerst

PHP
use App\Models\Order;

new class extends Component {
    public Order $order;
    public string $mode;

    public function mount(Order $order, string $mode = 'compact'): void
    {
        $this->order = $order;
        $this->mode  = $mode;
    }
};

#[Locked] ist so wichtig wie eh und je

PHP
use Livewire\Attributes\Locked;

new class extends Component {
    #[Locked]
    public int $invoiceId;   // der Client kann keine andere ID unterschieben

    public string $note = '';
};
Version 4 ändert nichts am Bedrohungsmodell: Jede öffentliche Eigenschaft ist vom Client beschreibbar, solange Sie sie nicht sperren, und jede öffentliche Methode ist ein erreichbarer Endpunkt. Siehe Kapitel 25.

6. wire:model in Version 4

Diese Änderung erwischt Sie am ehesten. Zwei Aspekte von wire:model verhalten sich in v4 anders, und beide scheitern lautlos statt mit einer Fehlermeldung.

Änderung 1: Modifikatoren steuern jetzt die clientseitige Synchronisation

In v3 entschieden .blur und .change nur, wann eine Netzwerkanfrage ausgelöst wird. In v4 entscheiden sie, wann der Wert überhaupt synchronisiert wird — auch auf dem Client. Für das alte Verhalten ergänzen Sie .live.

Blade
{{-- Livewire 3 --}}
<input wire:model.blur="title">

{{-- Das v4-Äquivalent dieses Verhaltens --}}
<input wire:model.live.blur="title">

Änderung 2: kein Event-Bubbling von Kindelementen mehr

wire:model auf einem Wrapper fängt keine Events mehr ab, die von verschachtelten Eingabefeldern hochblubbern. Wenn Sie sich darauf verlassen haben, ergänzen Sie .deep.

Blade
{{-- Livewire 3: der Wrapper fing das Event des Inputs ab --}}
<div wire:model="value">
    <input type="text">
</div>

{{-- Livewire 4: explizit wieder aktivieren --}}
<div wire:model.deep="value">
    <input type="text">
</div>

Alles andere bleibt gleich

Blade
{{-- Standardmäßig verzögert --}}
<input type="text" wire:model="name">

{{-- Anfrage bei jeder Änderung --}}
<input type="text" wire:model.live="search">

{{-- Live-Suche mit Debounce --}}
<input type="text" wire:model.live.debounce.400ms="search">

{{-- Verschachtelte Daten --}}
<input wire:model="form.address.city">
<input wire:model="post.title">
Die Bindung an eine Modelleigenschaft (post.title) verlangt weiterhin eine passende Validierungsregel. Das ist der Schutz vor Mass Assignment, und er ist unverändert in v4 übernommen worden.

7. Aktionen, Async und Renderless

Aktionen werden genau wie zuvor deklariert — als öffentliche Methoden, die aus dem Markup aufgerufen werden.

PHP
new class extends Component {
    public function save()
    {
        $this->validate();
        // …
    }
};
Blade
<button wire:click="save">Save</button>

<form wire:submit="save">…</form>

<button wire:click="delete({{ $post->id }})"
        wire:confirm="Delete this post? This cannot be undone.">Delete</button>

Aktionen ohne Neurendern

Ändert eine Aktion nichts, was der Nutzer sieht — einen Aufrufzähler erhöhen, eine Audit-Zeile schreiben —, spart das Überspringen des Neurenderns einen kompletten Template-Durchlauf und einen DOM-Diff.

PHP
use Livewire\Attributes\Renderless;

new class extends Component {
    #[Renderless]
    public function incrementViewCount(): void
    {
        $this->post->increment('views');
    }
};
Blade
{{-- Oder von der Template-Seite --}}
<button type="button" wire:click.renderless="incrementViewCount">Track</button>

Das imperative Gegenstück innerhalb einer Methode ist weiterhin $this->skipRender().

Asynchrone Aktionen

#[Async] (oder der Modifikator .async) führt eine Aktion parallel aus, außerhalb der normalen Anfrage-Warteschlange. Ideal für Fire-and-forget-Aufgaben: Analytics-Pings, Logging, Cache-Vorwärmung.

PHP
use Livewire\Attributes\Async;

new class extends Component {
    #[Async]
    public function logInteraction(string $element): void
    {
        Analytics::record($element, auth()->id());
    }
};
Blade
<button wire:click.async="logInteraction('cta-hero')">Get started</button>
Verwenden Sie niemals eine asynchrone Aktion, die im UI sichtbaren Zustand ändert. Da sie die Anfrage-Warteschlange umgeht, kann ihr Ergebnis relativ zu anderen Updates in falscher Reihenfolge eintreffen, und Sie erhalten einen Zustand, der dem Bildschirm widerspricht. Async ist für Seiteneffekte, die die Oberfläche nicht zurückliest.

Magische Aktionen

Blade
<button wire:click="$refresh">Refresh</button>
<button wire:click="$set('tab', 'settings')">Settings</button>
<button wire:click="$toggle('showFilters')">Filters</button>
<button wire:click="$dispatch('open-modal', { name: 'create' })">New</button>
<button wire:click="$parent.closeModal()">Close</button>
An der Sicherheit hat sich nichts geändert. Autorisieren Sie jeden Parameter serverseitig und markieren Sie Hilfsmethoden als protected oder private, damit sie nicht vom Client aufgerufen werden können.

8. Lebenszyklus und Hooks

Der Lebenszyklus ist gegenüber Version 3 unverändert. Die Reihenfolge zu kennen, beseitigt nach wie vor die meisten verwirrenden Fehler.

Erstes Rendern
1. Component instance created
2. boot()
3. mount($params)
4. booted()
5. render()
Folgendes Update
 1. POST /livewire-{hash}/update arrives with the state snapshot
 2. Snapshot checksum verified
 3. Component instance created
 4. boot()
 5. State restored from the snapshot
 6. hydrate() / hydrateFoo()
 7. booted()
 8. updating($prop, $value) / updatingFoo($value)
 9. Properties receive new values
10. updated($prop, $value) / updatedFoo($value)
11. Queued actions run
12. rendering() → render() → rendered($view, $html)
13. dehydrate() — state packed back into the snapshot
14. Response: markup + effects
PHP
new class extends Component {
    public float $price = 0;

    public function boot(): void {}
    public function booted(): void {}
    public function hydrate(): void {}
    public function dehydrate(): void {}

    public function updating(string $property, mixed $value): void {}

    public function updated(string $property, mixed $value): void
    {
        $this->validateOnly($property);
    }

    // Eigenschaftsspezifisch
    public function updatedPrice(mixed $value): void
    {
        $this->price = round((float) $value, 2);
    }

    // Verschachtelter Schlüssel form.email
    public function updatedFormEmail(mixed $value): void
    {
        $this->form['email'] = strtolower(trim($value));
    }
};
Islands ändern, wie viel gerendert wird, nicht die Reihenfolge dieser Hooks. Ein Island-Update durchläuft weiterhin den vollen serverseitigen Lebenszyklus — es liefert nur ein Fragment statt der ganzen Komponente.

9. Validierung und Form-Objekte

Die Validierung wird unverändert aus v3 übernommen, einschließlich Attributen und Form-Objekten.

PHP
use Livewire\Attributes\Validate;

new class extends Component {
    #[Validate('required|string|min:2|max:80')]
    public string $name = '';

    #[Validate('required|email:rfc,dns')]
    public string $email = '';

    #[Validate('accepted', message: 'Please agree to the processing of your data.')]
    public bool $consent = false;

    public function submit(): void
    {
        $data = $this->validate();

        Contact::create($data);

        $this->reset();
    }
};

Bedingte Regeln

PHP
protected function rules(): array
{
    return [
        'email' => [
            'required',
            'email',
            Rule::unique('users', 'email')->ignore($this->userId),
        ],
        'password' => $this->userId ? 'nullable|min:8|confirmed' : 'required|min:8|confirmed',
    ];
}

Form-Objekte

app/Livewire/Forms/PostForm.php
<?php

namespace App\Livewire\Forms;

use App\Models\Post;
use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    public ?Post $post = null;

    #[Validate('required|string|min:5|max:180')]
    public string $title = '';

    #[Validate('required|string|min:50')]
    public string $body = '';

    public function setPost(Post $post): void
    {
        $this->post  = $post;
        $this->title = $post->title;
        $this->body  = $post->body;
    }

    public function store(): Post
    {
        $this->validate();

        return Post::create($this->except('post'));
    }

    public function update(): void
    {
        $this->validate();

        $this->post->update($this->except('post'));
    }
}
Single-File-Komponente, die es verwendet
<?php

use Livewire\Component;
use App\Livewire\Forms\PostForm;

new class extends Component {
    public PostForm $form;

    public function save()
    {
        $this->form->post ? $this->form->update() : $this->form->store();

        $this->redirect(route('posts.index'), navigate: true);
    }
};

?>

<div>
    <form wire:submit="save">
        <input wire:model="form.title">
        @error('form.title') <span>{{ $message }}</span> @enderror

        <textarea wire:model="form.body"></textarea>
        @error('form.body') <span>{{ $message }}</span> @enderror

        <button type="submit">Save</button>
    </form>
</div>

Fehler in JavaScript

Version 4 stellt Validierungsfehler über die magische Eigenschaft $errors auch dem Client bereit.

Blade
<div x-show="$errors.has('email')" x-text="$errors.first('email')"></div>

10. Islands

Islands sind das Aushängeschild von Livewire 4 und die Neuerung, die das Denken über Komponentengröße verändert. Ein Island ist ein Bereich innerhalb einer Komponente, der sich unabhängig neu rendert: Bei einem Update wird nur dieses Fragment neu berechnet und gepatcht — nicht die ganze Komponente.

Blade
@island
    <div>Revenue: {{ $this->revenue }}</div>
@endisland

Warum das zählt

In v3 war das Standardmittel gegen „dieses Dashboard ist langsam“ die Aufteilung in sechs Kindkomponenten, damit ein Widget-Update nicht die Abfragen hinter den anderen fünf erneut ausführt. Islands liefern dieselbe Isolation ohne Komponentengrenze: eine Komponente, eine Klasse, mehrere unabhängig aktualisierende Bereiche.

Ein Dashboard mit drei Islands
<?php

use Livewire\Component;
use Livewire\Attributes\Computed;

new class extends Component {
    #[Computed]
    public function revenue()
    {
        return Order::whereMonth('created_at', now()->month)->sum('total');
    }

    #[Computed]
    public function queue()
    {
        return Job::pending()->count();
    }

    #[Computed]
    public function feed()
    {
        return Activity::latest()->take(20)->get();
    }
};

?>

<div>
    @island(name: 'revenue')
        <div class="card">
            <h3>Revenue this month</h3>
            <p class="stat">{{ number_format($this->revenue, 2) }} €</p>
        </div>
    @endisland

    @island(name: 'queue', poll: '5s')
        <div class="card">
            <h3>Jobs pending</h3>
            <p class="stat">{{ $this->queue }}</p>
        </div>
    @endisland

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

        <div class="card">
            @foreach ($this->feed as $activity)
                <div wire:key="activity-{{ $activity->id }}">{{ $activity->summary }}</div>
            @endforeach
        </div>
    @endisland
</div>

Optionen

OptionWirkung
nameBenennt das Island, damit Aktionen und JavaScript es adressieren können. Mehrere Islands mit gleichem Namen rendern immer als Gruppe.
lazyRendert, sobald das Island in den sichtbaren Bereich scrollt (Intersection Observer).
deferRendert direkt nach dem Laden der Seite, unabhängig von der Sichtbarkeit.
alwaysErzwingt ein Update des Islands bei jedem Neurendern des Elternteils.
skipÜberspringt das initiale Rendern vollständig.

Ein Island aus einer Aktion adressieren

Blade
@island(name: 'revenue')
    Revenue: {{ $this->revenue }}
@endisland

<button wire:click="$refresh" wire:island="revenue">Refresh revenue</button>

Append und Prepend — Endlos-Scrolling ohne Verkabelung

Blade
@island(name: 'feed')
    @foreach ($this->posts as $post)
        <article wire:key="post-{{ $post->id }}">{{ $post->title }}</article>
    @endforeach
@endisland

<button wire:click="loadMore" wire:island.append="feed">Load more</button>

Dasselbe, gesteuert aus Alpine oder reinem JavaScript:

Blade
<button x-on:click="$wire.$island('feed', { mode: 'append' }).loadMore()">
    Load more
</button>

Polling auf ein Island beschränkt

Blade
@island(name: 'queue')
    <div wire:poll.3s>
        Jobs pending: {{ $this->queue }}
    </div>
@endisland

Das Polling aktualisiert nur das Island. Zum Vergleich: In v3 führte wire:poll auf einem Dashboard alle paar Sekunden jede Abfrage der Seite erneut aus.

Island oder Kindkomponente. Verwenden Sie ein Island, wenn der Bereich den Zustand des Elternteils teilt und nur isoliertes Rendern braucht. Verwenden Sie eine Kindkomponente, wenn der Bereich eigenen Zustand, einen eigenen Lebenszyklus braucht oder an mehreren Stellen wiederverwendet wird.

11. Slots und Attribut-Weitergabe

Livewire-Komponenten nehmen jetzt Slot-Inhalte entgegen, wie Blade-Komponenten es immer konnten — mit einer wichtigen Besonderheit: Slot-Inhalt wird im Kontext des Elternteils ausgewertet. Ein wire:click in einem Slot ruft die Methode des Elternteils auf, nicht die des Kindes.

Standard-Slot

Eltern-Vorlage
<livewire:modal>
    <h2>Create a post</h2>

    <form wire:submit="save">
        <input wire:model="title">
        <button type="submit">Save</button>
    </form>
</livewire:modal>
resources/views/components/⚡modal.blade.php
<?php

use Livewire\Component;

new class extends Component {
    public bool $isOpen = false;

    public function toggle(): void
    {
        $this->isOpen = ! $this->isOpen;
    }
};

?>

<div wire:show="isOpen" class="modal">
    <button wire:click="toggle" class="modal__close">×</button>

    <div class="modal__body">
        {{ $slot }}
    </div>
</div>

In diesem Beispiel gehören wire:submit="save" und wire:model="title" zur Elternkomponente, während wire:click="toggle" zum Modal gehört. Genau diese Aufteilung wollen Sie: Das Modal ist für Öffnen und Schließen zuständig, das Elternteil für das Formular.

Benannte Slots

Eltern-Vorlage
<livewire:modal>
    <wire:slot name="header">
        <h2>Create a post</h2>
    </wire:slot>

    <form wire:submit="save">
        <input wire:model="title">
    </form>

    <wire:slot name="footer">
        <button wire:click="save">Save</button>
        <button wire:click="$parent.close()">Cancel</button>
    </wire:slot>
</livewire:modal>
Innerhalb der Komponente
<div class="modal">
    @if ($header = $slot('header'))
        <div class="modal__header">{{ $header }}</div>
    @endif

    <div class="modal__body">{{ $slot }}</div>

    @if ($footer = $slot('footer'))
        <div class="modal__footer">{{ $footer }}</div>
    @endif
</div>

Attribut-Weitergabe

Wie Blade-Komponenten kann auch eine Livewire-Komponente beliebige HTML-Attribute über $attributes entgegennehmen und zusammenführen.

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

use Livewire\Component;
use Livewire\Attributes\Prop;

new class extends Component {
    #[Prop]
    public string $type = 'info';
};

?>

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $slot }}
</div>
Blade
<livewire:alert type="danger" class="mt-6" data-testid="overdue-alert">
    This invoice is 14 days overdue.
</livewire:alert>
Wenn Sie eine ganzseitige Komponente rendern, können benannte Slots, die für das Layout bestimmt sind, außerhalb des Wurzelelements der Komponente platziert werden.

12. Verschachtelte Komponenten und wire:key

Verschachtelung funktioniert wie in v3: Jedes Kind ist eine eigenständige Einheit mit eigenem Zustand und eigenem Update-Zyklus.

Blade
<div>
    @foreach ($orders as $order)
        <livewire:order-row :order="$order" :key="'order-'.$order->id" />
    @endforeach
</div>

Smart Wire Keys

In Version 4 ist smart_wire_keys standardmäßig aktiv, sodass Livewire Keys in vielen Schleifensituationen selbst ableitet, in denen zuvor ein expliziter nötig war. Das reduziert Boilerplate, macht Keys aber nicht überflüssig.

Schreiben Sie Keys weiterhin, wo Identität zählt. Jede Liste, die umsortiert, gefiltert oder teilweise gelöscht werden kann, sollte einen expliziten wire:key tragen, der an die Datensatz-ID gebunden ist. Smart Keys sind eine Erleichterung für einfache Fälle, kein Ersatz dafür, dem Framework zu sagen, was eine Zeile tatsächlich ist.
Blade
@foreach ($rows as $row)
    <div wire:key="row-{{ $row->id }}">…</div>
@endforeach

Reaktive Props

PHP
use Livewire\Attributes\Reactive;

new class extends Component {
    #[Reactive]
    public int $quantity;
};

Das Elternteil aufrufen

Blade
<button wire:click="$parent.refreshList()">Refresh the list</button>

Zwei-Wege-Bindung an eine Komponente

PHP
use Livewire\Attributes\Modelable;

new class extends Component {
    #[Modelable]
    public int $value = 0;
};
Blade
<livewire:rating-input wire:model.live="review.rating" />
Mit Islands greifen Sie seltener zur Kindkomponente. War der einzige Grund für die Aufteilung „damit es sich eigenständig neu rendert“, ist ein Island leichter.

13. Events

Die Event-API ist gegenüber Version 3 unverändert.

PHP
// Auslösen
$this->dispatch('post-created');
$this->dispatch('post-created', postId: $post->id, title: $post->title);
$this->dispatch('refresh')->to(OrderList::class);
$this->dispatch('recalculate')->self();
PHP
use Livewire\Attributes\On;

new class extends Component {
    #[On('post-created')]
    public function onPostCreated(int $postId, string $title): void
    {
        // …
    }
};
Blade
<button wire:click="$dispatch('open-modal', { name: 'create-order' })">New order</button>

Events an den Browser

PHP
$this->dispatch('notify', type: 'success', message: 'Order saved');
Blade
<div x-data="{ show: false, message: '' }"
     x-on:notify.window="message = $event.detail.message; show = true; setTimeout(() => show = false, 3000)"
     x-show="show"
     x-transition
     class="toast">
    <span x-text="message"></span>
</div>
Islands ersetzen oft Events. Das klassische v3-Muster „Komponente A dispatcht, Komponente B lauscht und aktualisiert sich“ wird häufig zu „beide Bereiche sind Islands einer Komponente, und die Aktion adressiert das andere Island“ — eine Anfrage statt zwei.

14. Berechnete Eigenschaften

#[Computed] funktioniert wie in v3 und harmoniert besonders gut mit Islands: Ein Island, das eine berechnete Eigenschaft liest, berechnet sie nur neu, wenn dieses Island aktualisiert wird.

PHP
use Livewire\Attributes\Computed;

new class extends Component {
    public array $items = [];

    #[Computed]
    public function products()
    {
        return Product::whereIn('id', array_keys($this->items))->get();
    }

    #[Computed]
    public function subtotal(): float
    {
        return $this->products->sum(
            fn ($product) => $product->price * $this->items[$product->id]
        );
    }

    #[Computed]
    public function total(): float
    {
        return round($this->subtotal * 1.2, 2);
    }
};
Blade
@island(name: 'totals')
    <p>Subtotal: {{ number_format($this->subtotal, 2) }} €</p>
    <p>Total incl. VAT: {{ number_format($this->total, 2) }} €</p>
@endisland

Caching über die Anfrage hinaus

PHP
#[Computed(persist: true, seconds: 300)]
public function statistics(): array
{
    return [
        'orders'  => Order::whereMonth('created_at', now()->month)->count(),
        'revenue' => Order::whereMonth('created_at', now()->month)->sum('total'),
    ];
}

#[Computed(cache: true, key: 'global-stats')]
public function globalStats(): array
{
    return app(StatsService::class)->build();
}

Den Cache leeren

PHP
unset($this->products, $this->subtotal, $this->total);
In der Vorlage heißt eine berechnete Eigenschaft $this->products, niemals $products.

15. Zustand in URL und Session

PHP
use Livewire\Attributes\Url;
use Livewire\Attributes\Session;

new class extends Component {
    #[Url]
    public string $search = '';

    #[Url(as: 'cat')]
    public ?int $categoryId = null;

    #[Url(except: '')]
    public string $sort = 'popular';

    #[Url(keep: true)]
    public int $perPage = 24;

    #[Url(history: true)]
    public string $tab = 'all';

    #[Session]
    public bool $sidebarCollapsed = false;

    #[Session(key: 'admin.table.density')]
    public string $density = 'comfortable';
};

Ergebnis: /catalog?search=laptop&cat=5&sort=price-asc&tab=sale

Flash-Meldungen

PHP
public function save(): void
{
    $this->validate();

    Setting::updateOrCreate(['key' => 'theme'], ['value' => $this->theme]);

    session()->flash('status', 'Settings saved');
}
Blade
@if (session('status'))
    <div class="alert alert-success">{{ session('status') }}</div>
@endif
Jede #[Url]-Eigenschaft ruft bei Änderung die History-API auf. Bei Feldern, die sich pro Tastendruck ändern, kombinieren Sie das mit wire:model.live.debounce.

16. Datei-Uploads

Uploads funktionieren wie in Version 3 über den Trait WithFileUploads.

resources/views/components/⚡avatar-uploader.blade.php
<?php

use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;

new class extends Component {
    use WithFileUploads;

    #[Validate('required|image|mimes:jpg,jpeg,png,webp|max:4096')]
    public $avatar;

    public function save(): void
    {
        $this->validate();

        $path = $this->avatar->store('avatars', 'public');

        auth()->user()->update(['avatar_path' => $path]);

        $this->reset('avatar');

        $this->dispatch('notify', message: 'Avatar updated');
    }
};

?>

<div>
    <form wire:submit="save">
        <input type="file" wire:model="avatar" accept="image/*">

        <div wire:loading wire:target="avatar">Uploading…</div>

        @if ($avatar)
            <img src="{{ $avatar->temporaryUrl() }}" alt="Preview" class="preview">
        @endif

        @error('avatar') <p class="error">{{ $message }}</p> @enderror

        <button type="submit" wire:loading.attr="disabled">Save</button>
    </form>
</div>

Ein präziser Fortschrittsbalken

Blade
<div x-data="{ progress: 0, uploading: false }"
     x-on:livewire-upload-start="uploading = true"
     x-on:livewire-upload-finish="uploading = false; progress = 0"
     x-on:livewire-upload-error="uploading = false"
     x-on:livewire-upload-progress="progress = $event.detail.progress">

    <input type="file" wire:model="avatar">

    <div x-show="uploading" class="progress">
        <div class="progress__bar" :style="`width: ${progress}%`"></div>
    </div>
</div>
config/livewire.php
'temporary_file_upload' => [
    'disk'            => 's3',
    'rules'           => ['file', 'max:12288'],
    'directory'       => 'livewire-tmp',
    'middleware'      => 'throttle:60,1',
    'preview_mimes'   => ['png', 'jpeg', 'jpg', 'webp', 'gif', 'mp4', 'pdf'],
    'max_upload_time' => 5,
],
upload_max_filesize und post_max_size in PHP sowie client_max_body_size in nginx müssen alle Ihre max:-Regel übersteigen, sonst schlagen Uploads ohne verständliche Fehlermeldung fehl.

17. Paginierung

PHP
use Livewire\WithPagination;
use Livewire\Attributes\Url;

new class extends Component {
    use WithPagination;

    #[Url]
    public string $search = '';

    public string $sortField = 'created_at';
    public string $sortDirection = 'desc';

    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function sortBy(string $field): void
    {
        if ($this->sortField === $field) {
            $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortField = $field;
            $this->sortDirection = 'asc';
        }

        $this->resetPage();
    }

    public function with(): array
    {
        return [
            'orders' => Order::query()
                ->with('customer')
                ->when($this->search, fn ($q) => $q->where('number', 'like', "%{$this->search}%"))
                ->orderBy($this->sortField, $this->sortDirection)
                ->paginate(25),
        ];
    }
};

Islands machen aus Paginierung eine Endlosliste

Blade
<div>
    <input type="search" wire:model.live.debounce.400ms="search">

    @island(name: 'rows')
        @foreach ($orders as $order)
            <div wire:key="order-{{ $order->id }}">{{ $order->number }}</div>
        @endforeach
    @endisland

    <button wire:click="nextPage" wire:island.append="rows">Load more</button>
</div>
Bei großen Tabellen kostet paginate() ein zusätzliches COUNT(*). Wenn Sie keine Gesamtseitenzahl anzeigen — und beim Nachladen per Anhängen tun Sie das meist nicht —, ist simplePaginate() spürbar günstiger.

18. Ladezustände

Die wire:loading-Familie ist unverändert; v4 ergänzt data-loading-Attribute fürs Styling.

Blade
<div wire:loading>Loading…</div>
<div wire:loading.remove>Content</div>

<button wire:click="save">Save</button>
<span wire:loading wire:target="save">Saving…</span>

<span wire:loading wire:target="save,delete,publish">Working…</span>
<div wire:loading wire:target.except="search">Updating…</div>

<button wire:click="save" wire:loading.attr="disabled">Save</button>
<button wire:click="save" wire:loading.class="opacity-50 cursor-wait">Save</button>

{{-- Indikator nur anzeigen, wenn die Anfrage länger als der Schwellenwert dauert --}}
<div wire:loading.delay>Loading…</div>
<div wire:loading.delay.long>Loading…</div>

Styling aus CSS

CSS
[data-loading] .btn {
    opacity: 0.5;
    pointer-events: none;
}

Ungespeicherte Änderungen und Verbindungsstatus

Blade
<input wire:model="title">

<span wire:dirty wire:target="title">You have unsaved changes</span>

<div wire:offline class="banner banner--warn">
    No connection to the server. Changes are not being saved.
</div>

19. Optimistisches UI auf dem Client

Eine ganze Klasse von Interaktionen brauchte den Server nie: ein Panel aufklappen, einen Zeichenzähler anzeigen, ein Feld rot färben, wenn es zu lang wird. Version 4 gibt ihnen vollwertige Direktiven, die das DOM sofort ändern — ganz ohne Anfrage.

wire:show

Blade
{{-- Schaltet die Sichtbarkeit per CSS, sofort, ohne Round Trip --}}
<div wire:show="showModal" class="modal">…</div>

<button wire:click="$toggle('showModal')">Open</button>

wire:text

Blade
{{-- Der Text folgt der Eigenschaft auf dem Client --}}
<span wire:text="title"></span>

<input wire:model="title">

wire:bind

Blade
<textarea wire:model="message" maxlength="280"></textarea>

<span wire:text="message.length"></span> / 280

<span wire:bind:class="message.length > 240 && 'text-red-500'">
    Getting long
</span>

Rein clientseitige Aktionen mit $js

Blade
<?php

new class extends Component {
    public function save() { /* … */ }
};

?>

<div>
    <button wire:click="save">Save</button>

    {{-- Läuft ausschließlich auf dem Client --}}
    <button x-on:click="$wire.$js.showToast = true">Show toast</button>
</div>
Optimistisch heißt nicht maßgeblich. Diese Direktiven ändern, was der Nutzer sieht, bevor der Server zugestimmt hat. Alles, was Daten, Berechtigungen oder Geld betrifft, muss trotzdem serverseitig validiert und angewendet werden — der Client-Zustand ist eine Vorschau, keine verlässliche Datenquelle.
Faustregel: wire:show / wire:text für Rückmeldungen, die der Nutzer sofort spüren soll; eine normale Aktion für alles, was dauerhaft gespeichert werden muss.

20. Drag and Drop

Umsortieren bedeutete früher, SortableJS einzubinden, dessen Callbacks mit einer Livewire-Aktion zu verdrahten und beide Modelle synchron zu halten. In Version 4 ist es eine Direktive.

Blade
<ul wire:sort="reorder">
    @foreach ($tasks as $task)
        <li wire:sort:item="{{ $task->id }}" wire:key="task-{{ $task->id }}">
            {{ $task->title }}
        </li>
    @endforeach
</ul>
PHP
new class extends Component {
    public function reorder(array $order): void
    {
        foreach ($order as $position => $id) {
            Task::where('id', $id)->update(['position' => $position]);
        }
    }
};

Anfasser

Blade
<ul wire:sort="reorder">
    @foreach ($tasks as $task)
        <li wire:sort:item="{{ $task->id }}" wire:key="task-{{ $task->id }}">
            <span wire:sort:handle class="cursor-grab">⠿</span>

            <span>{{ $task->title }}</span>

            {{-- Interaktive Elemente sollen kein Ziehen auslösen --}}
            <button wire:sort:ignore wire:click="delete({{ $task->id }})">Delete</button>
        </li>
    @endforeach
</ul>

Zwischen Listen ziehen

Blade
<div class="board">
    @foreach ($columns as $column)
        <ul wire:sort="moveCard" wire:sort:group="board" wire:key="column-{{ $column->id }}">
            @foreach ($column->cards as $card)
                <li wire:sort:item="{{ $card->id }}" wire:key="card-{{ $card->id }}">
                    {{ $card->title }}
                </li>
            @endforeach
        </ul>
    @endforeach
</div>
Die Reihenfolge muss persistiert werden. Die Direktive sortiert das DOM optimistisch um und übergibt Ihnen die neue Reihenfolge — sie in die Datenbank zu schreiben ist Ihre Aufgabe. Wirft der Handler eine Exception, stimmen UI und Daten bis zum nächsten vollständigen Rendern nicht überein.

21. Scoped CSS und Skripte

Da eine Komponente jetzt eine einzige Datei ist, gehören auch ihre Styles und Skripte in diese Datei. Livewire begrenzt das CSS auf die Komponente, sodass es nicht in den Rest der Seite durchsickern kann.

resources/views/components/⚡pricing-card.blade.php
<?php

use Livewire\Component;
use Livewire\Attributes\Prop;

new class extends Component {
    #[Prop]
    public string $plan = 'starter';
};

?>

<div class="card">
    <h3 class="title">{{ ucfirst($plan) }}</h3>
    <p class="price">{{ $this->price }} € / month</p>
</div>

<style>
    /* Scoped to this component — no global .title collision */
    .card  { border: 1px solid #e4e2da; border-radius: 14px; padding: 24px; }
    .title { font-size: 20px; font-weight: 700; }
    .price { color: #2d6a4f; }
</style>

Bewusst globale Styles

Blade
<style global>
    :root { --brand: #f53004; }
</style>

Komponenten-Skripte

Blade
@script
<script>
    const chart = new Chart(document.getElementById('sales'), {
        type: 'line',
        data: @json($chartData),
    });

    $wire.on('data-updated', ({ points }) => {
        chart.data.datasets[0].data = points;
        chart.update();
    });
</script>
@endscript
Blade
{{-- Eine Drittanbieter-Bibliothek einmal für die ganze Seite laden --}}
@assets
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
@endassets
Scoped CSS ist eine Bequemlichkeit für komponentenlokale Gestaltung, kein Ersatz für ein Designsystem. Tokens, Typografie und Layout-Grundbausteine gehören in Ihr globales Stylesheet; die Handvoll Regeln, die nur diese Komponente betreffen, in die Komponente.

23. JavaScript-Integration

Alpine.js wird weiterhin mit Livewire ausgeliefert, und $wire bleibt die Brücke von JavaScript in den Komponentenzustand.

Blade
<div x-data="{ open: false }">
    <button x-on:click="open = !open">Details</button>

    <div x-show="open">
        <p x-text="$wire.title"></p>

        <button x-on:click="$wire.title = 'New title'">Rename</button>
        <button x-on:click="$wire.save()">Save</button>
        <button x-on:click="await $wire.calculate(); open = false">Calculate</button>
    </div>
</div>
Blade
{{-- Zwei-Wege-Bindung in Alpine --}}
<div x-data="{ query: $wire.entangle('search') }">
    <input x-model="query">
</div>

wire:ref — Elemente und Komponenten benennen

Blade
<livewire:modal wire:ref="modal" />

<button x-on:click="$refs.modal.open()">Open the modal</button>

#[Json] — Daten direkt an JavaScript zurückgeben

PHP
use Livewire\Attributes\Json;

new class extends Component {
    #[Json]
    public function searchSuggestions(string $term): array
    {
        return Product::search($term)->take(5)->pluck('name')->all();
    }
};
Blade
<input x-on:input.debounce.300ms="suggestions = await $wire.searchSuggestions($event.target.value)">

Interceptors

Version 4 ersetzt die v3-Hooks commit und request durch interceptMessage() und interceptRequest().

JavaScript
document.addEventListener('livewire:init', () => {

    Livewire.interceptRequest(({ options, fail }) => {
        options.headers['X-Tenant'] = window.tenantId;

        fail(({ status, preventDefault }) => {
            if (status === 419) {
                preventDefault();
                window.location.reload();   // das CSRF-Token ist abgelaufen
            }
        });
    });

    Livewire.interceptMessage(({ component, succeed }) => {
        succeed(() => {
            console.debug('component updated', component.name);
        });
    });
});
JavaScript
// Programmatischer Zugriff
Livewire.dispatch('refresh-orders');
Livewire.find('component-id').call('save');
Livewire.all().forEach((component) => component.$refresh());

Einen Teilbaum ignorieren

Blade
<div wire:ignore>
    <select id="select2-field">…</select>
</div>

<div wire:ignore.self>…</div>

24. Testing

Pest ist der empfohlene Weg, Livewire-4-Komponenten zu testen. View-basierte Komponenten können ihren Test direkt neben der Komponente ablegen statt in einem separaten Verzeichnisbaum.

Bash
php artisan make:livewire post.create --test
# resources/views/components/post/create.test.php

Eine view-basierte Komponente testen

Referenzieren Sie die Komponente über ihren Namen in Punktnotation statt über eine Klasse:

PHP
use Livewire\Livewire;

it('renders', function () {
    Livewire::test('post.create')
        ->assertStatus(200);
});

it('validates required fields', function () {
    Livewire::test('post.create')
        ->set('title', '')
        ->set('body', 'too short')
        ->call('save')
        ->assertHasErrors(['title' => 'required']);
});

it('stores a valid post', function () {
    Livewire::actingAs($user)
        ->test('post.create')
        ->set('title', 'A perfectly reasonable title')
        ->set('body', str_repeat('Body copy that is long enough. ', 3))
        ->call('save')
        ->assertHasNoErrors()
        ->assertDispatched('notify');

    expect(Post::where('title', 'A perfectly reasonable title')->exists())->toBeTrue();
});

Klassenbasierte Komponenten

PHP
use App\Livewire\Counter;

it('increments', function () {
    Livewire::test(Counter::class)
        ->assertSet('count', 0)
        ->call('increment')
        ->assertSet('count', 1);
});

Die wichtigsten Assertions

MethodePrüft
assertSet() / assertNotSet()Eigenschaftswerte
assertSee() / assertDontSee()Gerenderte Ausgabe
assertViewHas()An die View übergebene Daten
assertHasErrors() / assertHasNoErrors()Validierung
assertDispatched()Events
assertRedirect()Weiterleitungen
assertUnauthorized() / assertForbidden()Autorisierung
assertStatus()HTTP-Status
PHP
// Uploads
use Illuminate\Http\UploadedFile;

Livewire::test('avatar-uploader')
    ->set('avatar', UploadedFile::fake()->image('avatar.jpg', 400, 400))
    ->call('save')
    ->assertHasNoErrors();

// Eine Komponente innerhalb einer Seite
$this->get('/dashboard')
    ->assertSeeLivewire('pages::dashboard');
Testen Sie zuerst Autorisierung in Aktionen, Validierungsregeln und Zustandsübergänge. Dort bricht Funktionalität, und dort verstecken sich Sicherheitslücken — daran ändern Islands und Slots nichts.

25. Sicherheit und Performance

Das Bedrohungsmodell ist identisch mit v3. Jede öffentliche Methode ist ein offener HTTP-Endpunkt, jede öffentliche Eigenschaft vom Client beschreibbar. Single-File-Komponenten lassen sich leichter überfliegen — und damit überfliegt man auch leichter eine fehlende Autorisierungsprüfung.

Sicherheits-Checkliste

  • $this->authorize() in jeder Aktion, die Daten verändert.
  • Alle Identifier mit #[Locked] markiert.
  • Hilfsmethoden als protected oder private deklariert, damit der Client sie nicht aufrufen kann.
  • Validierungsregeln ohne interne Felder (user_id, role, price).
  • Keine Tokens, Schlüssel oder überflüssigen personenbezogenen Daten in öffentlichen Eigenschaften — der Snapshot geht im Klartext in den Browser.
  • Uploads nach MIME-Typ und Größe begrenzt.
  • Login- und Kontaktformulare hinter einem Rate Limiter.
  • {!! !!} nur für vertrauenswürdiges oder bereinigtes Markup.
  • Optimistisches UI gilt nie als maßgeblich.
PHP
public function delete(int $postId): void
{
    $post = Post::findOrFail($postId);

    $this->authorize('delete', $post);

    $post->delete();
}

Performance

SymptomLösung in v4
Ein Widget-Update stößt alle Abfragen der Seite anBereiche in @island einpacken
Riesiges Seiten-MarkupModel-Collections aus öffentlichen Eigenschaften herausnehmen; in with() oder #[Computed] laden
Eine Anfrage pro Tastendruckwire:model.live.debounce.400ms
Last durch PollingPolling im Island, plus .visible
Round Trip nur zum Umschalten eines Panelswire:show statt einer Aktion
Zähler-Inkrement rendert die Seite neu#[Renderless]
N+1-AbfragenEager Loading über with(); Model::preventLazyLoading() in der Entwicklung
Langsamer erster Aufbau bei einem schweren Block@island(lazy: true) mit einem @placeholder
app/Providers/AppServiceProvider.php
public function boot(): void
{
    Model::preventLazyLoading(! app()->isProduction());
}

26. Häufige Fehler und Spickzettel

Die Komponente rendert überhaupt nicht

Der Tag ist nicht selbstschließend. In v4 erzeugt <livewire:my-component> nichts — und zwar stillschweigend. Schreiben Sie <livewire:my-component />.

In der Produktion alles 404, lokal läuft es

Der Livewire-Endpunkt ist von /livewire/ nach /livewire-{hash}/ gewandert. Eine Firewall-Regel, WAF-Ausnahme, CDN-Regel oder nginx-Location, die noch auf den alten festen Pfad zeigt, blockiert die Update-Anfragen.

Ein Feld synchronisiert nach dem Upgrade nicht mehr

wire:model.blur und .change steuern jetzt die clientseitige Synchronisierung, nicht nur das Netzwerk. Ergänzen Sie .live: wire:model.live.blur="title".

Ein Wrapper greift sein Feld nicht mehr ab

Event-Bubbling ist standardmäßig deaktiviert. Verwenden Sie wire:model.deep.

wire:transition-Modifikatoren tun nichts

.opacity, .scale und .duration.200ms wurden entfernt — v4 nutzt die native View Transitions API. Beschreiben Sie die Animation stattdessen in CSS.

Ein Island aktualisiert nie

  • Das Island hat keinen name, die Aktion adressiert aber einen.
  • wire:island="…" zeigt auf einen Namen, den es in der gerenderten Ausgabe nicht gibt.
  • Der Bereich hängt an Elternzustand, der sich gar nicht geändert hat — always: true ergänzen.

Slot-Inhalt ruft die falsche Methode auf

Das ist so gewollt: Slot-Markup wird im Kontext der Elternkomponente ausgewertet. Um die Kindkomponente aufzurufen, gehen Sie über ein Event oder eine wire:ref.

Volt-Komponenten funktionieren nicht mehr

Volt ist in den Kern gewandert. Ersetzen Sie Livewire\Volt\Component durch Livewire\Component, Volt::route() durch Route::livewire(), Volt::test() durch Livewire::test() und entfernen Sie den Volt-Service-Provider und das Paket.

Direktiven-Spickzettel

DirektiveZweckNeu in v4
wire:modelFeld an Eigenschaft bindenSemantik geändert
wire:model.deepEvents von Kindelementen abfangenja
wire:click / wire:submitAktion aufrufen—
wire:click.asyncAktion parallel ausführenja
wire:click.renderlessNeurendern überspringenja
wire:islandEin Island aus einer Aktion adressierenja
wire:island.appendAn ein Island anhängen statt ersetzenja
wire:showSichtbarkeit auf dem Client schaltenja
wire:textText clientseitig bindenja
wire:bindAttribut reaktiv bindenja
wire:sortUmsortieren per Drag and Dropja
wire:intersectAktion beim Eintritt in den Viewportja
wire:refElement oder Komponente für JS benennenja
wire:navigateNavigation im SPA-Stil—
wire:navigate:scrollContainer-Scroll bewahrenumbenannt
wire:loading / wire:dirty / wire:offlineAnfragestatus—
wire:pollPeriodische Aktualisierungisland-begrenzt
wire:keyElement-Identität in SchleifenSmart Keys standardmäßig aktiv
wire:ignoreTeilbaum vom Morphing ausnehmen—

Blade-Direktiven

DirektiveZweck
@island … @endislandUnabhängig rendernder Bereich
@placeholder … @endplaceholderPlatzhalter für ein Lazy-Island
<wire:slot name="…">Inhalt eines benannten Slots
@script … @endscriptKomponenten-eigenes JavaScript
@assets … @endassetsSeiten-Assets, einmalig geladen
@persist … @endpersistElement über Navigationen hinweg erhalten
<style> / <style global>Scoped / globales Komponenten-CSS

PHP-Attribute

AttributZweck
#[Validate]Validierungsregel
#[Locked]Schreibzugriffe aus dem Frontend sperren
#[Computed]Gecachter abgeleiteter Wert
#[Url] / #[Session]Zustand erhalten
#[On]Event-Listener
#[Prop]Eine von der Elternkomponente gelieferte Prop deklarieren
#[Reactive] / #[Modelable]Bindung zwischen Eltern- und Kindkomponente
#[Async]Aktion parallel ausführen
#[Renderless]Neurendern überspringen
#[Json]Daten direkt an JavaScript zurückgeben
#[Lazy] / #[Layout] / #[Title]Laden und Seitenrahmen

Artisan-Befehle

Bash
php artisan make:livewire post.create              # Single-File
php artisan make:livewire post.create --mfc        # Multi-File
php artisan make:livewire post.create --test       # mit Test
php artisan make:livewire pages::dashboard         # mit Namespace
php artisan livewire:publish --config
php artisan optimize:clear

Offizielle Ressourcen

Livewire 4 entwickelt sich aktiv weiter. Wo sich diese Dokumentation und die offizielle Dokumentation widersprechen, gilt die offizielle — prüfen Sie sie, bevor Sie sich in der Produktion auf ein Detail verlassen.
Aufbereitet aus der offiziellen Dokumentation

Wenn diese Dokumentation und die offizielle Quelle voneinander abweichen, hat die offizielle Quelle recht. Einen Fehler gefunden? Sagen Sie uns Bescheid.