Skip to content

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:

Image

  • 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;

Image

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:

Image

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

Image

Die Protokolleinträge im Browser lauten dann wie folgt:

Image

  • 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:

Image

  • 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:

Image

  • 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:

Image

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:

Image

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

Image

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

Image

  • 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:

Image

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

Image

Wir stellen fest, dass auch hier die Sitzung reaktiv geworden ist: [2].

Rufen wir den von der Funktion [this.$session()] zurückgegebenen Wert ab:

Image

  • 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:

Image

  • 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.