Globaler State ohne Vuex oder Redux
Globaler State in großen JavaScript-Applikationen führt schnell zu Vuex, Redux oder Zustand. In Alpine.js-Projekten ist das eine Überreaktion. Alpine.store liefert reaktiven globalen State, der in jeder Komponente lesbar und schreibbar ist – ohne Build-Step, ohne Boilerplate, ohne neue Konzepte.
Inhaltsverzeichnis
- 1. Das Problem mit komponentenlokalem State
- 2. Alpine.store: Einrichtung und Grundprinzip
- 3. Store-Zugriff: $store, Alpine.store() und x-data
- 4. Actions und Getter im Store
- 5. Praxispatterns: Warenkorb, Notification, Auth
- 6. Store mit $persist kombinieren
- 7. Alpine.store in Hyvä und Magento 2
- 8. Alpine.store vs. Vuex vs. Redux im Vergleich
- 9. Zusammenfassung
- 10. FAQ
1. Das Problem mit komponentenlokalem State
Alpine.js-Komponenten mit x-data sind von Natur aus isoliert: Jede Komponente hat ihren eigenen State-Scope, der für andere Komponenten nicht zugänglich ist. Das ist in den meisten Fällen das gewünschte Verhalten – Kapselung ist ein Zeichen guten Komponentendesigns. Es gibt jedoch Situationen, in denen State von mehreren unabhängigen Komponenten gleichzeitig gelesen oder verändert werden muss. Ein Warenkorb-Zähler im Header soll sich aktualisieren, wenn ein Produkt auf der Produktseite hinzugefügt wird. Ein Toast-Notification-System soll von jeder Komponente ausgelöst werden können. Ein Authentifizierungsstatus soll global sichtbar sein und die Darstellung mehrerer Komponenten beeinflussen.
Ohne globalen State löst man diese Anforderungen häufig über Custom Events: Eine Komponente feuert ein Event, eine andere hört darauf. Das funktioniert, wird aber bei vielen Komponenten und komplexen Datenflüssen schwer zu überblicken. Alpine.store löst das Problem eleganter: Ein zentraler Store enthält den geteilten State, jede Komponente greift per $store.storeName.property lesend und schreibend darauf zu. Ändert eine Komponente einen Store-Wert, aktualisiert Alpine.js automatisch alle anderen Komponenten, die diesen Wert im Template referenzieren. Kein Event-System nötig, keine manuelle Synchronisation.
2. Alpine.store: Einrichtung und Grundprinzip
Ein Store wird mit Alpine.store('name', objekt) registriert. Das Objekt kann beliebige Werte und Methoden enthalten. Die Registrierung muss im alpine:init-Event oder vor Alpine.start() erfolgen. Danach ist der Store unter Alpine.store('name') in JavaScript und unter $store.name in jedem x-data-Kontext erreichbar. Das Reaktivitätssystem von Alpine.js macht jeden Store-Wert automatisch reaktiv: Templates, die auf Store-Eigenschaften referenzieren, aktualisieren sich bei Änderungen sofort. Das gilt auch für Werte, die aus JavaScript heraus verändert werden.
Ein Store ist ein einfaches JavaScript-Objekt – kein Proxy, kein Observable, keine spezielle Klasse. Alpine.js wandelt das Objekt intern in ein reaktives Proxy-Objekt um, ähnlich wie reactive() in Vue 3. Methoden des Store-Objekts haben Zugriff auf this und können Store-Eigenschaften lesen und schreiben. Das ermöglicht, Logik direkt im Store zu kapseln, anstatt sie in jede Komponente zu duplizieren. Ein Store kann auch einen init()-Hook haben, der beim Registrieren des Stores ausgeführt wird.
// Store vor Alpine.start() registrieren
document.addEventListener('alpine:init', () => {
// Einfacher Zähler-Store
Alpine.store('counter', {
count: 0,
increment() { this.count++; },
decrement() { this.count = Math.max(0, this.count - 1); },
reset() { this.count = 0; }
});
// Store mit init()-Hook
Alpine.store('catalog', {
products: [],
loading: false,
error: null,
async init() {
// Wird automatisch beim Registrieren ausgeführt
this.loading = true;
try {
const res = await fetch('/api/products?featured=1');
this.products = await res.json();
} catch (e) {
this.error = e.message;
} finally {
this.loading = false;
}
},
get featuredCount() {
return this.products.filter(p => p.featured).length;
}
});
});
Stores können nach der Registrierung aus JavaScript heraus verändert werden: Alpine.store('counter').count = 42 löst dieselbe Reaktivitätskette aus wie eine Änderung innerhalb des Stores selbst. Das ermöglicht, Stores von Legacy-Code, Server-Push-Events oder asynchronen Callbacks heraus zu aktualisieren, ohne dass der Code Alpine.js-spezifisch sein muss – er kennt nur die öffentliche API des Stores.
3. Store-Zugriff: $store, Alpine.store() und x-data
Im HTML-Template wird der Store über die magische Property $store zugegriffen. Diese ist in jedem x-data-Kontext verfügbar, auch wenn der Store-Name nicht in der Komponente deklariert wurde. $store.cart.count liest den aktuellen Wert, $store.cart.addItem(product) ruft eine Store-Methode auf. Store-Zugriffe im Template sind vollständig reaktiv – ändert sich cart.count, aktualisiert sich jedes Template, das diesen Wert bindet, automatisch.
Aus JavaScript heraus – also in x-init-Blöcken, Event-Handlern oder außerhalb von Alpine.js – nutzt man Alpine.store('name'). Das gibt das reaktive Store-Objekt zurück, auf dem gelesen und geschrieben werden kann. Wichtig: Stores sind global, es gibt keine Store-Namespaces. Bei größeren Projekten empfiehlt sich eine Namens-Konvention wie cart_v2 oder ein Präfix für Module.
// Im HTML-Template — kein x-data-Scope nötig für Store-Zugriff
// <div x-data>
// <span x-text="$store.cart.count">0</span>
// <button @click="$store.cart.addItem(product)">Hinzufügen</button>
// </div>
// Store-Zugriff aus Alpine.data() heraus
document.addEventListener('alpine:init', () => {
Alpine.data('productCard', () => ({
product: null,
adding: false,
async addToCart() {
this.adding = true;
// Store-Methode aufrufen
await Alpine.store('cart').addItem(this.product);
this.adding = false;
},
get isInCart() {
// Store-State als berechnete Eigenschaft nutzen
return Alpine.store('cart').items.some(i => i.id === this.product?.id);
}
}));
// Store von außen (kein x-data-Kontext) verändern
// Beispiel: Server-Sent Events aktualisieren Store
const evtSource = new EventSource('/api/stock-updates');
evtSource.onmessage = (e) => {
const update = JSON.parse(e.data);
Alpine.store('catalog').updateStock(update.productId, update.qty);
};
});
4. Actions und Getter im Store
Gut strukturierte Stores kapseln ihre gesamte Geschäftslogik intern. Actions sind Methoden des Store-Objekts, die State verändern. Getter sind berechnete Werte, die in JavaScript als get property() definiert werden und von Alpine.js reaktiv behandelt werden. Dieser Ansatz hält die Komponenten schlank: Sie rufen Store-Actions auf und lesen Store-Getter, enthalten aber selbst keine Geschäftslogik. Das Muster entspricht dem, was Vuex als Mutations/Actions/Getters kennt, ohne die Komplexität dieses Systems.
document.addEventListener('alpine:init', () => {
Alpine.store('cart', {
items: [],
coupon: null,
taxRate: 0.19,
// Actions: State verändern
addItem(product, qty = 1) {
const existing = this.items.find(i => i.id === product.id);
if (existing) {
existing.qty += qty;
} else {
this.items.push({ ...product, qty });
}
},
removeItem(productId) {
this.items = this.items.filter(i => i.id !== productId);
},
updateQty(productId, qty) {
const item = this.items.find(i => i.id === productId);
if (item) {
qty <= 0 ? this.removeItem(productId) : (item.qty = qty);
}
},
applyCoupon(code, discount) {
this.coupon = { code, discount };
},
clearCart() {
this.items = [];
this.coupon = null;
},
// Getter: berechnete Werte — reaktiv wie computed in Vue
get subtotal() {
return this.items.reduce((sum, i) => sum + i.price * i.qty, 0);
},
get discount() {
return this.coupon ? this.subtotal * this.coupon.discount : 0;
},
get tax() {
return (this.subtotal - this.discount) * this.taxRate;
},
get total() {
return this.subtotal - this.discount + this.tax;
},
get count() {
return this.items.reduce((sum, i) => sum + i.qty, 0);
},
get isEmpty() {
return this.items.length === 0;
}
});
});
5. Praxispatterns: Warenkorb, Notification, Auth
Drei Anwendungsfälle zeigen, wie Alpine.store in der Praxis eingesetzt wird. Der Warenkorb-Store zentralisiert alle Warenkorb-Operationen und sorgt dafür, dass Header-Badge, Minicart und Checkout-Button immer denselben Stand zeigen. Der Notification-Store ermöglicht es, von jeder Komponente aus Toast-Nachrichten auszulösen, die an einer zentralen Stelle im DOM gerendert werden. Der Auth-Store hält Authentifizierungsstatus und Benutzerdaten, die bereichsübergreifend sichtbar sein müssen – ohne dass jede Komponente individuell den Authentifizierungsstatus prüfen muss.
In Hyvä-Projekten auf Magento 2 werden Stores typischerweise in einem eigenen Inline-Script-Block registriert, der im Layout über ein dediziertes .phtml-Template eingebunden wird. Dieses Template enthält nur den Store-Registrierungscode und keine HTML-Ausgabe. Der $hyvaCsp->registerInlineScript()-Aufruf muss dabei immer folgen, um die Content-Security-Policy-Kompatibilität sicherzustellen.
6. Store mit $persist kombinieren
Die Kombination aus Alpine.store und dem $persist-Plugin ergibt globalen persistenten State: Werte, die in jedem Komponenten-Kontext zugänglich sind und Browser-Reloads überstehen. Das ist das Muster für Benutzerpräferenzen, Cookie-Consent und Theme-Einstellungen. Die Syntax ist dieselbe wie in einer normalen Komponente – $persist funktioniert direkt im Store-Objekt.
document.addEventListener('alpine:init', () => {
// Globaler persistenter Präferenzen-Store
Alpine.store('preferences', {
theme: Alpine.$persist('light').as('global_theme'),
language: Alpine.$persist('de').as('global_language'),
cookieConsent: Alpine.$persist(null).as('cookie_consent_v2'),
viewMode: Alpine.$persist('grid').as('catalog_view_mode'),
setTheme(theme) {
this.theme = theme;
document.documentElement.dataset.theme = theme;
},
acceptAllCookies() {
this.cookieConsent = {
analytics: true,
marketing: true,
functional: true,
timestamp: new Date().toISOString()
};
},
rejectOptionalCookies() {
this.cookieConsent = {
analytics: false,
marketing: false,
functional: true,
timestamp: new Date().toISOString()
};
},
get hasConsent() {
return this.cookieConsent !== null;
},
get needsCookieBanner() {
return !this.hasConsent;
},
init() {
// Theme beim Start anwenden
if (this.theme) {
document.documentElement.dataset.theme = this.theme;
}
}
});
});
7. Alpine.store in Hyvä und Magento 2
In Hyvä-Themes existieren bereits vorkonfigurierte Stores, die vom Theme bereitgestellt werden – zum Beispiel für den Warenkorb, den Kunden-Status und die Suchfunktion. Eigene Stores ergänzen diese, ohne mit ihnen zu kollidieren, wenn unterschiedliche Store-Namen verwendet werden. Die Registrierung in einem Layout-XML-referenzierten Template stellt sicher, dass der Store verfügbar ist, bevor Komponenten auf ihn zugreifen.
Ein typisches Hyvä-Muster ist das Registrieren eines projektspezifischen Stores in einem default_head_blocks.xml-Layout-Block. Das Template wird im <head> der Seite geladen, wodurch der Store für alle Komponenten im Body sofort verfügbar ist. Der $hyvaCsp->registerInlineScript()-Aufruf nach jedem <script>-Block ist dabei Pflicht für CSP-Kompatibilität in Magento 2.4.x.
8. Alpine.store vs. Vuex vs. Redux im Vergleich
Der Vergleich zwischen Alpine.store, Vuex und Redux verdeutlicht, für welche Projektgröße und Komplexität Alpine.store die richtige Wahl ist. Für die meisten Hyvä- und Magento-2-Frontend-Anforderungen ist Alpine.store ausreichend und deutlich schlanker.
| Aspekt | Alpine.store | Vuex 4 | Redux Toolkit |
|---|---|---|---|
| Bundle-Größe | Im Alpine-Bundle (~15 KB) | ~30 KB zusätzlich | ~60 KB zusätzlich |
| Boilerplate | Minimal — ein Objekt | Mutations/Actions/Getters trennen | Slices, Reducers, Selectors |
| Build-Step | Keiner nötig | Empfohlen (Vite/webpack) | Praktisch Pflicht |
| DevTools-Integration | Eingeschränkt | Vue DevTools, Zeitreise-Debugging | Redux DevTools, vollständig |
| Skalierbarkeit | Bis ~10–20 Stores gut | Großanwendungen | Enterprise-Skala |
9. Zusammenfassung
Alpine.store ist die schlanke, pragmatische Antwort auf die Frage nach globalem reaktivem State in Alpine.js-Projekten. Es braucht keine zusätzlichen Bibliotheken, keinen Build-Step und kein neues Konzept außer einem JavaScript-Objekt. Actions kapseln die Logik, Getter berechnen abgeleitete Werte, und das Reaktivitätssystem von Alpine.js sorgt dafür, dass alle Komponenten automatisch aktuell bleiben. In Kombination mit dem $persist-Plugin wird der Store persistent, in Kombination mit Custom Events lässt er sich in bestehende Magento-2-Systeme integrieren.
Die Grenzen von Alpine.store liegen bei sehr großen Applikationen mit Hunderten von Store-Einträgen, komplexen asynchronen Flows und dem Bedarf nach Zeitreise-Debugging. Für diese Szenarien sind Vuex oder Redux die richtigen Werkzeuge. Für Hyvä-Themes, Magento-2-Frontend-Entwicklung und mittelgroße Projekte mit Alpine.js ist Alpine.store in den meisten Fällen genau das richtige Werkzeug – weder zu wenig noch zu viel.
Alpine.js Store — Das Wichtigste auf einen Blick
Registrierung
Alpine.store('name', objekt) im alpine:init-Event. Stores sind sofort in allen Templates per $store.name zugänglich.
Actions & Getter
Methoden als Actions, get property() als reaktive Getter. Logik gehört in den Store, nicht in die Komponenten.
Reaktivität
Jede Änderung eines Store-Werts aktualisiert automatisch alle Templates, die ihn referenzieren – ohne manuelles Event-System.
Persistenz
$persist direkt im Store-Objekt – globaler persistenter State ohne zusätzliche Infrastruktur.
Mironsoft
Alpine.js, Hyvä-Themes und Magento-2-Frontend-Entwicklung
State-Architektur für euer Hyvä-Projekt?
Wir entwerfen skalierbare Store-Strukturen für Alpine.js-Projekte – von einfachen Präferenz-Stores bis hin zu komplexen Warenkorb- und Authentifizierungs-Flows in Hyvä und Magento 2.
Store-Design
Skalierbare Store-Strukturen für komplexe Magento-2-Frontends entwerfen
Hyvä-Integration
Bestehende Hyvä-Stores erweitern und mit Custom-Stores verknüpfen
Migration
Zustandsbehafteten jQuery-Code zu Alpine.store migrieren ohne Funktionsverlust
10. FAQ: Alpine.js Store
1Alpine.store vs. x-data?
$store.name oder Alpine.store('name') erreichbar.2Wann Alpine.store verwenden?
3Mehrere Stores möglich?
cart, preferences, notifications etc.4Ist Alpine.store reaktiv?
5Berechnete Werte im Store?
get total() { return this.subtotal + this.tax; } – Alpine behandelt sie reaktiv.6$persist im Store?
Alpine.$persist('light').as('global_theme') direkt im Store – globaler persistenter State ohne Boilerplate.7Store aus Alpine.data() aufrufen?
this.$store.name.prop. In JavaScript: Alpine.store('name').prop. Beide Wege reaktiv.8init()-Hook im Store?
init()-Methode im Store-Objekt wird beim Registrieren automatisch aufgerufen. Ideal für API-Abrufe beim Start.9Store in Hyvä einbinden?
.phtml im alpine:init-Event. $hyvaCsp->registerInlineScript() nach jedem Script. Layout-XML im <head>.10Store von außen verändern?
Alpine.store('name').property = value aus jedem JS-Kontext. Reaktivität wird ausgelöst – ideal für Server-Sent Events oder Legacy-Code.