9. Beispiel [nuxt-06]: Einfügung im Kontext eines Session-Managers
9.1. Présentation
Das Beispiel [nuxt-05] hat gezeigt, dass der Store auch dann beibehalten werden kann, wenn der Benutzer Aufrufe an den Server erzwingt. Die Elemente des Stores sind reaktiv, sodass sie, wenn sie in Ansichten integriert sind, auf Änderungen im Store reagieren. Es kann auch vorkommen, dass man Elemente über den gesamten Client-Server-Austausch hinweg beibehalten möchte, ohne dass sie reaktiv sind – einfach deshalb, weil sie nicht in Ansichten angezeigt werden. Diese können dann in der Sitzung gespeichert werden, ohne dass sie sich im Store befinden.
Auf den Store kann einfach über Eigenschaften wie [context.app.$store] außerhalb von Ansichten oder [this.$store] in Ansichten zugegriffen werden. Man würde sich etwas Ähnliches für die Sitzung wünschen, etwa [context.app.$session] oder [this.$session]. Wir werden sehen, dass dies dank des Konzepts der Injektion möglich ist. Allerdings können keine Objekte in den Kontext injiziert werden, sondern nur Funktionen. Diese steht dann über die Ausdrücke [context.app.$session()] oder [this.$session()] zur Verfügung.
Abschließend stellen wir das Konzept [nuxt] von [plugin] vor.
Das Beispiel [nuxt-06] wird zunächst durch Kopieren des Projekts [nuxt-05] erstellt:

- in [1]. Wir fügen einen Ordner [plugins] hinzu;
9.2. das Konzept des Plugins [nuxt]
[nuxt] bezeichnet als [plugin] jeden Code, der beim Start der Anwendung ausgeführt wird, noch bevor die Funktion [nuxtServerInit] vom Server ausgeführt wird, die bisher die erste auszuführende Benutzerfunktion war. Die Plugins der Anwendung müssen im Schlüssel [plugins] der Konfigurationsdatei [nuxt.config.js] deklariert werden:
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/client/session', mode: 'client' },
{ src: '~/plugins/server/session', mode: 'server' }
],
- Zeilen 5–6: Ein Plugin wird durch seinen Pfad [src] und seinen Ausführungsmodus [mode] bezeichnet. [mode] kann drei Werte annehmen:
- [client]: Das Plugin muss ausschließlich clientseitig ausgeführt werden;
- [server]: Das Plugin muss ausschließlich serverseitig ausgeführt werden;
- Fehlt der Schlüssel [mode]: In diesem Fall muss das Plugin sowohl clientseitig als auch serverseitig ausgeführt werden;
- Zeilen 5–6: Wir haben unsere beiden Plugins in einem Ordner namens [plugins] abgelegt. Dies ist jedoch nicht zwingend erforderlich. Die Plugins können an beliebiger Stelle in der Projektstruktur abgelegt werden. Ebenso sind die Namen der Unterordner [client, server] hier willkürlich gewählt;

9.3. Das Plugin [session] des Servers
Das Plugin [server / session.js] sieht wie folgt aus:
/* eslint-disable no-console */
export default (context, inject) => {
// Verwaltung der Serversitzung
// Gibt es eine bestehende Sitzung?
let value = context.app.$cookies.get('session')
if (!value) {
// Neue Sitzung
console.log("[plugin session server], démarrage d'une nouvelle session")
value = initValue
} else {
// bestehende Sitzung
console.log("[plugin session server], reprise d'une session existante")
}
// Definition der Sitzung
const session = {
// Inhalt der Sitzung
value,
// Speicherung der Sitzung in einem Cookie
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
// Es wird eine Funktion in [context, Vue] eingefügt, die die aktuelle Sitzung aktiviert
inject('session', () => session)
}
// Anfangswert der Sitzung
const initValue = {
initSessionDone: false
}
- Zeile 2: Die Plugins werden bei jedem Aufruf des Servers ausgeführt: beim Start und jedes Mal, wenn der Benutzer einen Aufruf des Servers manuell durch Eingabe von URL erzwingt:
- Zunächst wird das (oder werden die) Server-Plugin(s) ausgeführt;
- sobald der Client-Browser die Antwort vom Server erhalten hat, sind die Client-Plugins an der Reihe;
- Zeile 2: Jedes Plugin, egal ob auf dem Server oder auf dem Client, erhält zwei Parameter:
- [context]: den Server- oder Client-Kontext, je nachdem, wer das Plugin ausführt;
- [inject]: eine Funktion, mit der eine Funktion in den Server- oder Client-Kontext eingefügt werden kann;
- Das Plugin [server / session] verfolgt zwei Ziele:
- eine Sitzung definieren (Zeilen 16–23);
- innerhalb des Kontexts eine Funktion [$session] zu definieren, die als Ergebnis die Sitzung aus Zeile 16 zurückgibt. Dies geschieht in Zeile 25;
- Zeilen 16–23: Die Sitzung kapselt ihre Daten in das Objekt [value] aus Zeile 18;
- Zeilen 20–22: Sie verfügt über eine Funktion [save], die als Parameter ein Objekt [context] erhält. Der aufrufende Code stellt ihr diesen Kontext zur Verfügung. Mit diesem speichert die Funktion [save] den Wert der Sitzung, das Objekt [value], im Sitzungscookie;
- Zeile 6: Wenn das Plugin [server / session] ausgeführt wird, prüft es zunächst, ob der Server ein Sitzungs-Cookie erhalten hat;
- Wenn ja, stellt das Objekt [value] in Zeile 6 den Wert der Sitzung dar, also die Gesamtheit der darin gekapselten Daten;
- Wenn nicht, werden in den Zeilen 7–11 der Anfangswert der Sitzung festgelegt. Dieser ist das Objekt [initValue] aus den Zeilen 29–31. Die Elemente der Sitzung werden in der Funktion [nuxtServerInit] definiert, die nach dem Server-Plugin ausgeführt wird;
- Zeile 18: Die Notation [value] ist eine Abkürzung für die Notation [value:value]. Das links stehende [value] ist der Name eines Objektschlüssels, das rechts stehende [value] ist das in Zeile 6 deklarierte Objekt [value];
- Zeile 25: Wenn diese Zeile erreicht wird, wurde die Sitzung entweder angelegt, weil sie noch nicht existierte, oder aus der Anfrage HTTP des Client-Browsers abgerufen;
- Zeile 25: In den Serverkontext wird eine neue Funktion eingefügt:
- Der erste Parameter von [inject] ist der Name der Funktion, die erstellt wird, hier „session“. [nuxt] weist ihr tatsächlich den Namen „$session“ zu;
- Der zweite Parameter ist die Definition der Funktion. Hier akzeptiert die Funktion [$session]
- keine Parameter akzeptiert;
- gibt das Objekt „[session]“ aus Zeile 16 zurück;
- Sobald das Plugin ausgeführt wurde:
- ist die Funktion „[$session]“ in „[context.app.$session]“ verfügbar, wo das Objekt „[context]“ verfügbar ist, oder [this.$session] in einer Ansicht oder im Store [vuex];
- die Funktion [$session] gibt ein Objekt [session] mit einem eindeutigen Schlüssel [value] zurück;
- Bei der erstmaligen Erstellung der Sitzung hat das Objekt [value] nur einen Schlüssel [initStoreDone] (Zeilen 29–31). Der Schlüssel [initStoreDone:false] dient dazu, anzugeben, dass der Store noch nicht in die Sitzung aufgenommen wurde. Dies erfolgt durch die Funktion [nuxtServerInit];
9.4. Initialisierung der Sitzung
Sobald das Plugin [session / server] vom Server ausgeführt wurde, führt dieser das folgende Skript [store / index.js] aus:
/* eslint-disable no-console */
export const state = () => ({
// Zähler
counter: 0
})
export const mutations = {
// Erhöhung des Zählers um einen Wert [inc]
increment(state, inc) {
state.counter += inc
},
// Ersetzen des Zustands
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// Wer führt diesen Code aus?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// Wir warten auf das Ende eines Promises
await new Promise(function(resolve, reject) {
// Normalerweise gibt es hier eine asynchrone Funktion
// Wir simulieren sie mit einer Wartezeit von einer Sekunde
setTimeout(() => {
// Sitzung initialisieren
initSession(store, context)
// Erfolg
resolve()
}, 1000)
})
}
}
function initSession(store, context) {
// „store“ ist der zu initialisierende Speicher
// Die Sitzung wird abgerufen
const session = context.app.$session()
// Wurde die Sitzung bereits initialisiert?
if (!session.value.initSessionDone) {
// Ein neuer Store wird gestartet
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// Der Store wird initialisiert
store.commit('increment', 77)
// Der Speicher wird in die Sitzung aufgenommen
session.value.store = store.state
// Eine neue Sitzung wird initialisiert
session.value.somethingImportant = { x: 2, y: 4 }
// Die Sitzung ist nun initialisiert
session.value.initSessionDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// Der Speicher wird mit dem Speicherstand der Sitzung aktualisiert
store.commit('replace', session.value.store)
}
// Die Sitzung wird gespeichert
session.save(context)
// Protokoll
console.log('initSession terminé, store=', store.state, 'session=', session.value)
}
Im Vergleich zum Store des Projekts [nuxt-05] ändert sich lediglich die Funktion [initSession] (früher initStore) in den Zeilen 38–60:
- Zeile 42: Die Sitzung wird mithilfe der Funktion [$session] abgerufen, die in den Serverkontext eingefügt wurde;
- Zeile 44: Es wird geprüft, ob die Sitzung bereits initialisiert wurde;
- Zeilen 45–54: Ist dies nicht der Fall:
- Zeile 48: Der Store wird initialisiert;
- Zeile 50: Der Status des Stores wird in die Sitzung geschrieben;
- Zeile 52: Ein weiteres Objekt [somethingImportant] wird der Sitzung hinzugefügt. Dieses wird nicht Teil des Speichers sein;
- Zeile 54: Es wird vermerkt, dass die Sitzung nun initialisiert ist;
- Zeilen 55–59: Wenn die Sitzung bereits initialisiert war:
- Zeile 58: Der neue Speicher wird mit dem Inhalt der Sitzung initialisiert;
- Zeile 61: Die Sitzung wird im Sitzungs-Cookie gespeichert. Zur Erinnerung: Dabei wird das Cookie in die Antwort HTTP eingefügt, die der Server an den Client-Browser sendet;
9.5. Das Client-Plugin [client / session]
Sobald der Server die Skripte [plugins / server / session] und [store / index] ausgeführt hat, sendet er eine der Seiten [index, page1] an den Client-Browser. In der Antwort HTTP des Servers ist das Sitzungs-Cookie enthalten. Sobald die Seite vom Browser des Clients empfangen wurde, werden die in die Seite eingebetteten Client-Skripte ausgeführt. Das Plugin [client / session] wird dann ausgeführt:
/* eslint-disable no-console */
export default (context, inject) => {
// Verwaltung der Client-Sitzung
// Die Sitzung ist zwangsläufig vorhanden und wird vom Server initialisiert
console.log('[plugin session client], reprise de la session du serveur')
// Definition der Sitzung
const session = {
// Inhalt der Sitzung
value: context.app.$cookies.get('session'),
// Speicherung der Sitzung in einem Cookie
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
// Es wird eine Funktion in [context, Vue] eingefügt, die die aktuelle Sitzung herstellt
inject('session', () => session)
}
- Wenn das Client-Plugin ausgeführt wird, wurde das Sitzungs-Cookie bereits vom Client-Browser empfangen;
- Das Ziel des Plugins [client] besteht darin, ebenfalls eine Funktion [$session] in den Client-Kontext einzufügen. Diese Funktion würde die vom Server gesendete Sitzung wiederherstellen;
- Zeile 19: Die eingefügte Funktion [$session] gibt die Sitzung aus den Zeilen 9–16 zurück;
- Zeilen 9–16: das vom Client verwaltete Objekt [session]. Dabei handelt es sich um eine Kopie der vom Server gesendeten Sitzung;
- Zeile 11: Der Wert der Client-Sitzung wird aus dem vom Server gesendeten Sitzungs-Cookie [nuxt] entnommen;
- Zeilen 13–15: Wie bei der Serversitzung verfügt auch die Clientsitzung über eine Funktion [save], mit der der Wert der Sitzung, [this.value] (Zeile 14), im im Browser gespeicherten Sitzungs-Cookie gespeichert werden kann;
9.6. Die Seite [index]
Die Seite [index] entwickelt sich wie folgt:
<!-- Seite [index] -->
<template>
<Layout :left="true" :right="true">
<!-- Navigation -->
<Navigation slot="left" />
<!-- Meldung-->
<template slot="right">
<b-alert show variant="warning"> Home - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- Schaltfläche -->
<b-button @click="incrementCounter" class="ml-3" variant="primary">Incrémenter</b-button>
</template>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
import Layout from '@/components/layout'
import Navigation from '@/components/navigation'
export default {
name: 'Home',
// verwendete Komponenten
components: {
Layout,
Navigation
},
computed: {
jsonSession() {
return JSON.stringify(this.$session().value)
}
},
// Lebenszyklus
beforeCreate() {
// Client und Server
console.log('[home beforeCreate]')
},
created() {
// Client und Server
console.log('[home created], session=', this.$session().value)
},
beforeMount() {
// nur Client
console.log('[home beforeMount]')
},
mounted() {
// nur Client
console.log('[home mounted]')
},
// Ereignisverwaltung
methods: {
incrementCounter() {
console.log('incrementCounter')
// Zähler um 1 erhöhen
this.$store.commit('increment', 1)
// Sitzung ändern
const session = this.$session()
session.value.store = this.$store.state
session.value.somethingImportant.x++
session.value.somethingImportant.y++
// Speichern der Sitzung im Sitzungs-Cookie
session.save(this.$nuxt.context)
}
}
}
</script>
Man muss bedenken, dass diese Seite sowohl serverseitig als auch clientseitig ausgeführt wird.
- Zeile 8: Nun werden sowohl die Sitzung als auch der Speicher angezeigt;
- Zeile 30: [jsonSession] ist eine berechnete Eigenschaft, die die Zeichenkette jSON aus dem Wert der Sitzung bildet;
- Zeile 41: Der Wert der Sitzung wird mithilfe der eingefügten Funktion [this.$session] angezeigt. Diese existiert sowohl im Server- als auch im Client-Kontext;
- Zeile 53: Die Methode [incrementCounter] wird nur auf der Client-Seite ausgeführt;
- Zeile 56: Der Zähler des Stores wird erhöht und wie zuvor angezeigt;
- Zeile 58: Die Sitzung wird mithilfe der eingefügten Funktion [this.$session] abgerufen;
- Zeile 59: Der Speicher der Sitzung wird aktualisiert;
- Zeilen 60–61: Die Attribute [somethingImportant.x, somethingImportant.y] der Sitzung werden erhöht. Dies dient lediglich dazu zu zeigen, dass eine Sitzung dazu dienen kann, andere Daten als den Speicher zu transportieren;
- Zeile 63: Die Sitzung wird im Sitzungs-Cookie gespeichert, das im Browser abgelegt ist. In einer Client-Ansicht ist dessen Kontext in [this.$nuxt.context] verfügbar;
Der Zweck der Seite [index] besteht darin, zu zeigen, dass die Sitzung nicht reaktiv ist, während der Store es ist. Wenn man die Elemente der Sitzung inkrementiert, wird man feststellen, dass die Ansicht nicht aktualisiert wird. Die Ansicht [page1] bietet eine Lösung für dieses Problem.
9.7. Die Seite [page1]
Die Seite [page1] wurde durch Kopieren der Seite [index] erstellt und anschließend leicht modifiziert:
<!-- Seite [index] -->
<template>
<Layout :left="true" :right="true">
<!-- Navigation -->
<Navigation slot="left" />
<!-- Meldung-->
<template slot="right">
<b-alert show variant="warning"> Page1 - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- Schaltfläche -->
<b-button @click="incrementCounter" class="ml-3" variant="primary">Incrémenter</b-button>
</template>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
import Layout from '@/components/layout'
import Navigation from '@/components/navigation'
export default {
name: 'Page1',
// verwendete Komponenten
components: {
Layout,
Navigation
},
data() {
return {
session: {}
}
},
computed: {
jsonSession() {
return JSON.stringify(this.session.value)
}
},
// Lebenszyklus
beforeCreate() {
// Client und Server
console.log('[page1 beforeCreate]')
},
created() {
// Client und Server
// Die Sitzung wird in die reaktiven Eigenschaften der Seite aufgenommen
this.session = this.$session()
// Protokoll
console.log('[page1 created], session=', this.session.value)
},
beforeMount() {
// nur Client
console.log('[page1 beforeMount]')
},
mounted() {
// nur Client
console.log('[page1 mounted]')
},
// Ereignisverwaltung
methods: {
incrementCounter() {
console.log('incrementCounter')
// Zähler um 1 erhöhen
this.$store.commit('increment', 1)
// Sitzungsänderung
this.session.value.store = this.$store.state
this.session.value.somethingImportant.x++
this.session.value.somethingImportant.y++
// Speichern der Sitzung im Sitzungs-Cookie
this.session.save(this.$nuxt.context)
}
}
}
</script>
- Zeile 47: Der Hauptunterschied besteht darin, dass die aktuelle Sitzung in die Eigenschaften der Seite aufgenommen wird (Zeilen 29–33). Dies hat zur Folge, dass die Sitzung nun reaktiv wird. Wenn die Funktion [incrementCounter] die Elemente der Sitzung inkrementiert, wird die Ansicht [page1] aktualisiert;
9.8. Ausführung des Projekts
Bevor Sie das Projekt ausführen, überprüfen Sie das Sitzungs-Cookie Ihres Browsers und löschen Sie es gegebenenfalls, damit der Server eine neue Sitzung erstellt:

Rufen wir nun die URL und [http://localhost:81/nuxt-06/] auf:

Die Protokolleinträge im Browser lauten dann wie folgt:

- In [2] startet der Server eine neue Sitzung im Plugin [session] des Servers;
- in [3] wird diese neue Sitzung in [nuxtServerInit] initialisiert;
- in [4] ist die neue Sitzung so, wie sie auf dem Server bekannt ist;
- in [5] hat der Client diese Sitzung korrekt abgerufen;
Nun erhöhen wir den Zähler dreimal:

- in [3] wurde der Zähler zwar erhöht, die Sitzung in [2] jedoch nicht. Während [3] den reaktiven Speicher anzeigt, zeigt [2] die Sitzung an, die nicht reaktiv ist:
Laden wir nun die Seite neu (F5). Nach diesem Neuladen lauten die Protokolle wie folgt:

- In [2] ist zu sehen, dass der Server ein vom Client-Browser gesendetes Sitzungs-Cookie empfangen hat;
- in [4] ist zu sehen, dass der Speicher nicht zurückgesetzt, sondern aus der empfangenen Sitzung übernommen wurde;
- In [4-5] sieht man, dass die Attribute der Sitzung tatsächlich alle dreimal erhöht wurden;
Die vom Server gesendete Seite sieht dann wie folgt aus:

Die Schlussfolgerung aus dieser Seite lautet, dass die Sitzung zwar andere Elemente als den Speicher transportieren kann, diese jedoch nicht reaktiv sind.
Klicken wir nun auf den Link [Page 1] [4]. Die daraufhin angezeigte neue Seite sieht wie folgt aus:

Klicken wir nun dreimal auf die Schaltfläche „[Incrémenter]“. Die Seite sieht dann wie folgt aus:

Diesmal wird die Sitzung korrekt unter [2] angezeigt. Sie ist hier reaktiv. Dies lässt sich in den Protokollen erkennen:

- in [1-3], die Werte der Sitzung;
- in [4-6] die reaktiven Getter und Setter der Session-Elemente;
Klicken wir nun auf den Link [Home] [4]. Wir gelangen auf die folgende Seite:

Klicken wir anschließend zweimal auf die Schaltfläche [Incrémenter] [4]. Die Seite sieht nun wie folgt aus:

Wir stellen fest, dass auch hier die Sitzung reaktiv geworden ist: [2].
Rufen wir den von der Funktion [this.$session()] zurückgegebenen Wert ab:

- Auf der Registerkarte „[Vue]“ wählen wir die aktuelle Seite „[Home]“ aus, um deren Referenz „[$vm0] [3]“ zu erhalten;
Anschließend rufen wir auf der Registerkarte [Console] [4] den Wert der Funktion [$vm0.$session()] ab:

- In [5] sieht man, dass die Sitzung nun aktiv ist, obwohl sie es ursprünglich nicht war;
- In [6] fordern wir den Wert der Sitzung ab;
- In [7-8] stellt man fest, dass auch dieser Wert nun reaktiv geworden ist;
Wir haben es hier also mit einem unerwarteten Ergebnis zu tun: Wenn ein Element auf einer Seite reaktiv wird, weil es in den Eigenschaften der Seite festgelegt wurde, dann wird es auch auf den Seiten reaktiv, auf denen es nicht Teil der Eigenschaften ist.
9.9. Conclusion
Das Beispiel [nuxt-05] hat gezeigt, dass der Store über mehrere Serveranfragen hinweg beibehalten werden kann. Das Beispiel [nuxt-06] macht dasselbe mit einem Objekt, das wir in Anlehnung an die Web-Sitzung [session] genannt haben. Wir haben gesehen, dass diese Sitzung dieselben Eigenschaften wie der Speicher [Vuex] aufweisen und ebenfalls reaktiv werden kann, obwohl sie von Haus aus nicht reaktiv war.
Was ist also der Sinn des Stores [Vuex]? Ich muss zugeben, dass mir das bisher noch nicht klar geworden ist. Wahrscheinlich ist mir etwas entgangen. Im Zweifelsfall würde ich daher empfehlen, Folgendes zu verwenden:
- einen Speicher [Vuex], um dort alles abzulegen, was zwischen den Seiten des Clients geteilt werden muss, sowie alles, was eventuell zwischen dem Client und dem Server geteilt werden muss;
- ein Session-Cookie, falls der Speicher bei einem Aufruf vom Client zum Server erhalten bleiben soll, wobei die Session dann nur den Speicher enthält;
Die Beispiele [nuxt-05] und [nuxt-06] sollten zeigen, wie man die Kontinuität der Anwendung gewährleisten kann, wenn der Benutzer den Aufruf an den Server erzwingt, indem er URL manuell eingibt. Es sei daran erinnert, dass das Standardverhalten in diesem Fall ein Neustart der Anwendung ist, wobei ihr aktueller Zustand verloren geht.