9. Beispiel [nuxt-06]: Injektion im Kontext eines Session-Managers
9.1. Übersicht
Das Beispiel [nuxt-05] hat gezeigt, dass der Store auch dann beibehalten werden kann, wenn der Benutzer Serveraufrufe erzwingt. Store-Elemente sind reaktiv, sodass diese Views auf Änderungen im Store reagieren, wenn sie in Views integriert sind. Möglicherweise möchten Sie Elemente auch während des Client-Server-Austauschs beibehalten, ohne dass sie reaktiv sind, einfach weil sie nicht von Views angezeigt werden. Sie können diese dann in der Sitzung speichern, ohne dass sie im Store enthalten sind.
Auf den Store kann einfach über Eigenschaften wie [context.app.$store] außerhalb von Views oder [this.$store] innerhalb von Views zugegriffen werden. Wir wünschen uns etwas Ähnliches für die Sitzung, etwa [context.app.$session] oder [this.$session]. Wir werden sehen, dass dies dank des Konzepts der Injektion möglich ist. Allerdings können wir keine Objekte in den Kontext injizieren, sondern nur Funktionen. Diese Funktion steht dann über die Ausdrücke [context.app.$session()] oder [this.$session()] zur Verfügung.
Abschließend stellen wir das [nuxt]-Konzept von [plugin] vor.
Das Beispiel [nuxt-06] wird zunächst durch Klonen des Projekts [nuxt-05] erstellt:

- In [1] fügen wir einen Ordner [plugins] hinzu;
9.2. Das Konzept eines [nuxt]-Plugins
[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 vom Benutzer ausgeführte Funktion 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] identifiziert. [mode] kann drei Werte annehmen:
- [client]: Das Plugin muss ausschließlich auf der Client-Seite ausgeführt werden;
- [server]: Das Plugin darf nur auf der Serverseite ausgeführt werden;
- Der Schlüssel [mode] fehlt: In diesem Fall muss das Plugin sowohl auf der Client- als auch auf der Serverseite ausgeführt werden;
- Zeilen 5–6: Wir haben unsere beiden Plugins in einem Ordner [plugins] abgelegt. Dies ist nicht zwingend erforderlich. Plugins können an beliebiger Stelle in der Verzeichnisstruktur des Projekts abgelegt werden. Ebenso sind die Namen der Unterordner [client, server] hier beliebig;

9.3. Das Server-[session]-Plugin
Das [server / session.js]-Plugin sieht wie folgt aus:
/* eslint-disable no-console */
export default (context, inject) => {
// server session management
// is there an existing session?
let value = context.app.$cookies.get('session')
if (!value) {
// new session
console.log("[plugin session server], démarrage d'une nouvelle session")
value = initValue
} else {
// existing session
console.log("[plugin session server], reprise d'une session existante")
}
// session definition
const session = {
// session content
value,
// save the session in a cookie
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
// we inject a function into [context, Vue] that will render the current session
inject('session', () => session)
}
// initial session value
const initValue = {
initSessionDone: false
}
- Zeile 2: Plugins werden bei jeder Serveranfrage ausgeführt: beim Start und jedes Mal, wenn der Benutzer durch manuelle Eingabe einer URL eine Serveranfrage auslöst:
- Zuerst werden die Server-Plugins ausgeführt;
- sobald der Client-Browser die Antwort des Servers erhalten hat, sind die Client-Plugins an der Reihe;
- Zeile 2: Jedes Plugin, egal ob client- oder serverseitig, erhält zwei Parameter:
- [context]: der Server- oder Client-Kontext, je nachdem, welcher das Plugin ausführt;
- [inject]: eine Funktion, die es ermöglicht, eine Funktion in den Server- oder Client-Kontext einzufügen;
- Das [server / session]-Plugin hat zwei Aufgaben:
- eine Sitzung zu definieren (Zeilen 16–23);
- eine Funktion [$session] innerhalb des Kontexts zu definieren, die die Sitzung aus Zeile 16 zurückgibt. Dies geschieht in Zeile 25;
- Zeilen 16–23: Die Sitzung kapselt ihre Daten im [value]-Objekt in Zeile 18;
- Zeilen 20–22: Sie verfügt über eine [save]-Funktion, die ein [context]-Objekt als Parameter entgegennimmt. Der aufrufende Code stellt diesen Kontext bereit. Damit speichert die [save]-Funktion den Sitzungswert, das [value]-Objekt, im Sitzungscookie;
- Zeile 6: Wenn das [server / session]-Plugin ausgeführt wird, prüft es zunächst, ob der Server ein Session-Cookie empfangen hat;
- Wenn ja, repräsentiert das [value]-Objekt aus Zeile 6 den Sitzungswert, also die darin gekapselten Daten;
- Wenn nicht, legen wir in den Zeilen 7–11 den Anfangswert der Sitzung fest. Dies ist das [initValue]-Objekt in den Zeilen 29–31. Die Sitzungselemente werden in der Funktion [nuxtServerInit] definiert, die nach dem Server-Plugin ausgeführt wird;
- Zeile 18: Die Notation [value] ist eine Kurzform für die Notation [value:value]. Das [value] auf der linken Seite ist der Name eines Objektschlüssels; das [value] auf der rechten Seite ist das in Zeile 6 deklarierte [value]-Objekt;
- Zeile 25: Wenn diese Zeile erreicht wird, wurde die Sitzung entweder erstellt, weil sie noch nicht existierte, oder aus der HTTP-Anfrage des Client-Browsers abgerufen;
- Zeile 25: Wir fügen eine neue Funktion in den Serverkontext ein:
- Der erste Parameter von [inject] ist der Name der zu erstellenden Funktion, hier „session“. [nuxt] wird ihr tatsächlich den Namen „$session“ geben;
- Der zweite Parameter ist die Funktionsdefinition. Hier akzeptiert die Funktion [$session]
- keine Parameter akzeptieren;
- gibt das [session]-Objekt aus Zeile 16 zurück;
- Sobald das Plugin ausgeführt wurde:
- ist die Funktion [$session] in [context.app.$session] verfügbar, wo immer das [context]-Objekt verfügbar ist, oder in [this.$session] in einer Ansicht oder im [vuex]-Store;
- Die Funktion [$session] gibt ein [session]-Objekt mit einem einzigen [value]-Schlüssel zurück;
- Bei der anfänglichen Erstellung der Sitzung hat das [value]-Objekt nur einen Schlüssel [initStoreDone] (Zeilen 29–31). Der Schlüssel [initStoreDone:false] zeigt an, dass der Store noch nicht zur Sitzung hinzugefügt wurde. Dies wird von der Funktion [nuxtServerInit] übernommen;
9.4. Initialisierung der Sitzung
Sobald das [session / server]-Plugin vom Server ausgeführt wird, führt der Server das folgende [store / index.js]-Skript aus:
/* eslint-disable no-console */
export const state = () => ({
// meter
counter: 0
})
export const mutations = {
// increment counter by one [inc] value
increment(state, inc) {
state.counter += inc
},
// state replacement
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// who executes this code?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// waiting for a promise to be fulfilled
await new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// init session
initSession(store, context)
// success
resolve()
}, 1000)
})
}
}
function initSession(store, context) {
// store is the blind to be initialized
// retrieve the session
const session = context.app.$session()
// has the session already been initiated?
if (!session.value.initSessionDone) {
// start a new blind
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// initialize the blind
store.commit('increment', 77)
// put the blind in the session
session.value.store = store.state
// initialize a new session
session.value.somethingImportant = { x: 2, y: 4 }
// the session is now initialized
session.value.initSessionDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// update the store with the session store
store.commit('replace', session.value.store)
}
// save the session
session.save(context)
// log
console.log('initSession terminé, store=', store.state, 'session=', session.value)
}
Im Vergleich zum Store im Projekt [nuxt-05] hat sich lediglich die Funktion [initSession] (früher initStore) in den Zeilen 38–60 geändert:
- Zeile 42: Wir rufen die Sitzung mit der Funktion [$session] ab, die in den Serverkontext injiziert wurde;
- Zeile 44: Wir prüfen, ob die Sitzung bereits initialisiert wurde;
- Zeilen 45–54: Falls nicht:
- Zeile 48: Der Store wird initialisiert;
- Zeile 50: Der Status des Speichers wird in die Sitzung geschrieben;
- Zeile 52: Wir fügen der Sitzung ein weiteres Objekt [somethingImportant] hinzu. Dieses Objekt wird nicht Teil des Speichers sein;
- Zeile 54: Wir stellen fest, 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 Sitzungscookie gespeichert. Beachten Sie, dass hierfür das Cookie in die HTTP-Antwort eingefügt wird, die der Server an den Browser des Clients sendet;
9.5. Das [client / session]-Plugin des Clients
Sobald der Server die Skripte [plugins / server / session] und [store / index] ausgeführt hat, sendet er eine der Seiten [index, page1] an den Browser des Clients. Die HTTP-Antwort des Servers enthält das Sitzungscookie. Sobald die Seite vom Browser des Clients empfangen wurde, werden die in die Seite eingebetteten clientseitigen Skripte ausgeführt. Das Plugin [client / session] wird dann ausgeführt:
/* eslint-disable no-console */
export default (context, inject) => {
// customer session management
// the session necessarily exists, initialized by the server
console.log('[plugin session client], reprise de la session du serveur')
// session definition
const session = {
// session content
value: context.app.$cookies.get('session'),
// save the session in a cookie
save(context) {
context.app.$cookies.set('session', this.value, { path: context.base, maxAge: context.env.maxAge })
}
}
// we inject a function into [context, Vue] that will render the current session
inject('session', () => session)
}
- Wenn das Client-Plugin ausgeführt wird, hat der Client-Browser das Session-Cookie bereits empfangen;
- Das Ziel des [client]-Plugins ist es, zusätzlich eine Funktion [$session] in den Client-Kontext einzufügen. Diese Funktion würde die vom Server gesendete Sitzung zurückgeben;
- Zeile 19: Die eingefügte Funktion [$session] gibt die Sitzung aus den Zeilen 9–16 zurück;
- Zeilen 9–16: Das vom Client verwaltete [session]-Objekt. Dies ist eine Kopie der vom Server gesendeten Sitzung;
- Zeile 11: Der Wert der Client-Sitzung wird aus dem vom [nuxt]-Server gesendeten Sitzungscookie abgerufen;
- Zeilen 13–15: Wie bei der Server-Session verfügt auch die Client-Session über eine [save]-Funktion, die es ermöglicht, den Session-Wert [this.value] in Zeile 14 im im Browser gespeicherten Session-Cookie zu speichern;
9.6. Die [index]-Seite
Die [index]-Seite entwickelt sich wie folgt:
<!-- page [index] -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message-->
<template slot="right">
<b-alert show variant="warning"> Home - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- bouton -->
<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',
// components used
components: {
Layout,
Navigation
},
computed: {
jsonSession() {
return JSON.stringify(this.$session().value)
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[home beforeCreate]')
},
created() {
// client and server
console.log('[home created], session=', this.$session().value)
},
beforeMount() {
// customer only
console.log('[home beforeMount]')
},
mounted() {
// customer only
console.log('[home mounted]')
},
// event management
methods: {
incrementCounter() {
console.log('incrementCounter')
// counter increment of 1
this.$store.commit('increment', 1)
// session modification
const session = this.$session()
session.value.store = this.$store.state
session.value.somethingImportant.x++
session.value.somethingImportant.y++
// save session in session cookie
session.save(this.$nuxt.context)
}
}
}
</script>
Beachten Sie, dass diese Seite sowohl auf dem Server als auch auf dem Client ausgeführt wird.
- Zeile 8: Wir zeigen nun sowohl die Sitzung als auch den Speicher an;
- Zeile 30: [jsonSession] ist eine berechnete Eigenschaft, die den JSON-String des Sitzungswerts zurückgibt;
- Zeile 41: Wir zeigen den Sitzungswert mithilfe der injizierten Funktion [this.$session] an. 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 Blindzähler wird erhöht und wie zuvor angezeigt;
- Zeile 58: Die Sitzung wird mithilfe der injizierten Funktion [this.$session] abgerufen;
- Zeile 59: Der Session-Store wird aktualisiert;
- Zeilen 60–61: Wir inkrementieren die Session-Attribute [somethingImportant.x, somethingImportant.y]. Dies dient lediglich dazu zu zeigen, dass eine Session dazu verwendet werden kann, andere Daten als den Store zu transportieren;
- Zeile 63: Die Sitzung wird im im Browser gespeicherten Sitzungscookie gespeichert. In einer Client-Ansicht ist der Sitzungskontext unter [this.$nuxt.context] verfügbar;
Der Zweck der Seite [index] besteht darin, zu demonstrieren, dass die Sitzung nicht reaktiv ist, während der Store es ist. Wenn wir die Sitzungselemente inkrementieren, werden wir sehen, 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] wird erstellt, indem die Seite [index] kopiert und anschließend leicht modifiziert wird:
<!-- page [index] -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message-->
<template slot="right">
<b-alert show variant="warning"> Page1 - session= {{ jsonSession }}, counter= {{ $store.state.counter }} </b-alert>
<!-- bouton -->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
session: {}
}
},
computed: {
jsonSession() {
return JSON.stringify(this.session.value)
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page1 beforeCreate]')
},
created() {
// client and server
// set the session in the page's reactive properties
this.session = this.$session()
// log
console.log('[page1 created], session=', this.session.value)
},
beforeMount() {
// customer only
console.log('[page1 beforeMount]')
},
mounted() {
// customer only
console.log('[page1 mounted]')
},
// event management
methods: {
incrementCounter() {
console.log('incrementCounter')
// counter increment of 1
this.$store.commit('increment', 1)
// session modification
this.session.value.store = this.$store.state
this.session.value.somethingImportant.x++
this.session.value.somethingImportant.y++
// save session in session cookie
this.session.save(this.$nuxt.context)
}
}
}
</script>
- Zeile 47: Der Hauptunterschied besteht darin, dass wir die aktuelle Sitzung zu den Eigenschaften der Seite hinzufügen (Zeilen 29–33). Dadurch wird die Sitzung reaktiv. Wenn die Funktion [incrementCounter] die Elemente der Sitzung erhöht, wird die Ansicht [page1] aktualisiert;
9.8. Ausführen des Projekts
Bevor Sie das Projekt ausführen, überprüfen Sie das Sitzungscookie Ihres Browsers und löschen Sie es gegebenenfalls, damit der Server eine neue Sitzung erstellt:

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

Die Browser-Protokolle sehen dann wie folgt aus:

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

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

- In [2] sehen wir, dass der Server ein vom Client-Browser gesendetes Sitzungscookie empfangen hat;
- in [4] sehen wir, dass der Speicher nicht zurückgesetzt, sondern aus der empfangenen Sitzung übernommen wird;
- in [4-5]: Wir sehen, dass die Sitzungsattribute 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 neben dem Speicher auch andere Elemente enthalten kann, diese jedoch nicht reaktiv sind.
Klicken wir nun auf den Link [Seite 1] [4]. Die neue Seite sieht wie folgt aus:

Klicken wir nun dreimal auf die Schaltfläche [Erhöhen]. Die Seite sieht nun wie folgt aus:

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

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

Klicken Sie dann zweimal auf die Schaltfläche [Increment] [4]. Die Seite ändert sich wie folgt:

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

- Wählen Sie auf der Registerkarte [Ansicht] die aktuelle Seite [Home] aus, um deren Referenz [$vm0] zu erhalten [3];
Rufen wir dann auf der Registerkarte [Console] [4] den Wert der Funktion [$vm0.$session()] ab:

- In [5] sehen wir, dass die Sitzung nun reaktiv ist, während sie es anfangs nicht war;
- in [6] fragen wir den Wert der Sitzung ab;
- in [7-8] stellen wir fest, dass auch dieser Wert reaktiv geworden ist;
Wir haben hier also ein unerwartetes Ergebnis: Wenn ein Element auf einer Seite reaktiv wird, weil es in die Eigenschaften der Seite aufgenommen wurde, wird es auch auf Seiten reaktiv, auf denen es nicht Teil der Eigenschaften ist.
9.9. Fazit
Beispiel [nuxt-05] zeigte, dass wir den Store über mehrere Anfragen an den Server hinweg beibehalten konnten. Beispiel [nuxt-06] macht dasselbe mit einem Objekt, das wir in Anlehnung an die Web-Session [session] genannt haben. Wir haben gesehen, dass diese Session dieselben Eigenschaften wie der [Vuex]-Store haben und ebenfalls reaktiv werden konnte, obwohl sie ursprünglich nicht reaktiv war.
Was ist also der Sinn des [Vuex]-Stores? Ich muss zugeben, dass mir das derzeit noch nicht ganz klar ist. Wahrscheinlich habe ich etwas übersehen. Im Zweifelsfall würde ich daher empfehlen, Folgendes zu verwenden:
- einen [Vuex]-Store, um alles zu speichern, was zwischen clientseitigen Seiten geteilt werden muss, sowie alles, was möglicherweise zwischen Client und Server geteilt werden muss;
- ein Session-Cookie, falls der Store während einer Client-zu-Server-Anfrage persistent sein muss, wobei die Session nur den Store enthält;
Die Beispiele [nuxt-05] und [nuxt-06] sollten zeigen, wie die Kontinuität der Anwendung gewährleistet werden kann, wenn der Benutzer durch manuelle Eingabe von URLs einen Aufruf an den Server erzwingt. Beachten Sie, dass das Standardverhalten in diesem Fall ein Neustart der Anwendung ist, wodurch ihr aktueller Zustand verloren geht.