9. Esempio [nuxt-06]: Iniezione nel contesto di un gestore di sessioni
9.1. Panoramica
L'esempio [nuxt-05] ha mostrato che lo store può essere mantenuto anche quando l'utente forza le chiamate al server. Gli elementi dello store sono reattivi, quindi se vengono integrati nelle viste, queste ultime reagiscono alle modifiche nello store. Potresti anche voler mantenere gli elementi durante gli scambi client/server senza che siano reattivi, semplicemente perché non vengono visualizzati dalle viste. Puoi quindi memorizzarli nella sessione senza che siano presenti nello store.
È possibile accedere facilmente allo store tramite proprietà come [context.app.$store] al di fuori delle viste o [this.$store] all'interno delle viste. Vorremmo qualcosa di simile per la sessione, qualcosa come [context.app.$session] o [this.$session]. Vedremo che ciò è possibile grazie al concetto di iniezione. Tuttavia, non possiamo iniettare oggetti nel contesto, ma solo funzioni. Questa funzione sarà quindi disponibile tramite le espressioni [context.app.$session()] o [this.$session()].
Infine, introdurremo il concetto di [plugin] in [nuxt].
L'esempio [nuxt-06] viene inizialmente creato clonando il progetto [nuxt-05]:

- In [1], aggiungeremo una cartella [plugins];
9.2. Il concetto di plugin [nuxt]
[nuxt] definisce [plugin] come qualsiasi codice eseguito all'avvio dell'applicazione, anche prima che la funzione [nuxtServerInit] venga eseguita dal server, che fino ad ora era la prima funzione utente ad essere eseguita. I plugin dell'applicazione devono essere dichiarati nella chiave [plugins] del file di configurazione [nuxt.config.js]:
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/client/session', mode: 'client' },
{ src: '~/plugins/server/session', mode: 'server' }
],
- righe 5-6: un plugin è identificato dal suo percorso [src] e dalla sua modalità di esecuzione [mode]. [mode] può assumere tre valori:
- [client]: il plugin deve essere eseguito solo sul lato client;
- [server]: il plugin deve essere eseguito solo sul lato server;
- chiave [mode] mancante: in questo caso, il plugin deve essere eseguito sia sul lato client che su quello server;
- righe 5-6: abbiamo collocato i nostri due plugin in una cartella [plugins]. Ciò non è obbligatorio. I plugin possono essere collocati ovunque nella struttura delle directory del progetto. Analogamente, i nomi delle sottocartelle [client, server] sono qui arbitrari;

9.3. Il plugin [session] del server
Il plugin [server / session.js] è il seguente:
/* 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
}
- Riga 2: I plugin vengono eseguiti ogni volta che c'è una richiesta al server: all'avvio e ogni volta che l'utente forza una richiesta al server inserendo manualmente un URL:
- prima vengono eseguiti i plugin del server;
- una volta che il browser client ha ricevuto la risposta del server, è il turno dei plugin client di essere eseguiti;
- Riga 2: Ogni plugin, sia lato client che lato server, riceve due parametri:
- [context]: il contesto server o client, a seconda di quale sta eseguendo il plugin;
- [inject]: una funzione che consente di inserire una funzione nel contesto server o client;
- Lo scopo del plugin [server / session] è duplice:
- definire una sessione (righe 16–23);
- Definire una funzione [$session] all'interno del contesto che restituisce la sessione dalla riga 16. La riga 25 esegue questa operazione;
- righe 16–23: la sessione incapsulerà i propri dati nell'oggetto [value] alla riga 18;
- righe 20–22: dispone di una funzione [save] che accetta un oggetto [context] come parametro. Il codice chiamante fornisce questo contesto. Con esso, la funzione [save] salva il valore della sessione, l'oggetto [value], nel cookie di sessione;
- riga 6: quando il plugin [server / session] viene eseguito, verifica innanzitutto se il server ha ricevuto un cookie di sessione;
- in tal caso, l'oggetto [value] della riga 6 rappresenta il valore della sessione, ovvero l'insieme di dati incapsulati al suo interno;
- in caso contrario, alle righe 7–11, impostiamo il valore iniziale della sessione. Questo sarà l'oggetto [initValue] alle righe 29–31. Gli elementi della sessione saranno definiti nella funzione [nuxtServerInit], che viene eseguita dopo il plugin del server;
- riga 18: la notazione [value] è una scorciatoia per la notazione [value:value]. Il [value] a sinistra è il nome di una chiave dell'oggetto; il [value] a destra è l'oggetto [value] dichiarato alla riga 6;
- riga 25: quando si raggiunge questa riga, la sessione è stata creata perché non esisteva, oppure è stata recuperata dalla richiesta HTTP del browser del client;
- riga 25: inseriamo una nuova funzione nel contesto del server:
- il primo parametro di [inject] è il nome della funzione che si sta creando, in questo caso "session". [nuxt] le assegnerà effettivamente il nome "$session";
- il secondo parametro è la definizione della funzione. Qui, la funzione [$session]
- non accetterà alcun parametro;
- restituisce l'oggetto [session] della riga 16;
- una volta eseguito il plugin:
- la funzione [$session] è disponibile in [context.app.$session] ovunque sia disponibile l'oggetto [context], oppure in [this.$session] in una vista o nello store [vuex];
- la funzione [$session] restituisce un oggetto [session] con una singola chiave [value];
- Alla creazione iniziale della sessione, l'oggetto [value] ha una sola chiave [initStoreDone] (righe 29–31). La chiave [initStoreDone:false] indica che lo store non è ancora stato aggiunto alla sessione. Ciò verrà fatto dalla funzione [nuxtServerInit];
9.4. Inizializzazione della sessione
Una volta che il plugin [session / server] viene eseguito dal server, il server eseguirà il seguente script [store / index.js]:
/* 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)
}
Rispetto allo store nel progetto [nuxt-05], è cambiata solo la funzione [initSession] (precedentemente initStore) nelle righe 38–60:
- riga 42: recuperiamo la sessione utilizzando la funzione [$session], che è stata iniettata nel contesto del server;
- riga 44: verifichiamo se la sessione è già stata inizializzata;
- righe 45–54: in caso contrario:
- riga 48: lo store viene inizializzato;
- riga 50: lo stato dello store viene inserito nella sessione;
- riga 52: aggiungiamo un altro oggetto [somethingImportant] alla sessione. Questo oggetto non farà parte dello store;
- riga 54: notiamo che la sessione è ora inizializzata;
- righe 55–59: se la sessione era già stata inizializzata:
- riga 58: il nuovo archivio viene inizializzato con il contenuto della sessione;
- riga 61: la sessione viene salvata nel cookie di sessione. Si noti che ciò comporta l'inserimento del cookie nella risposta HTTP che il server invierà al browser del cliente;
9.5. Il plugin [client / session] del client
Una volta che il server ha eseguito gli script [plugins / server / session] e [store / index], invierà una delle pagine [index, page1] al browser del client. La risposta HTTP del server includerà il cookie di sessione. Una volta che la pagina è stata ricevuta dal browser del client, verranno eseguiti gli script lato client incorporati nella pagina. Verrà quindi eseguito il plugin [client / session]:
/* 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)
}
- Quando il plugin client viene eseguito, il cookie di sessione è già stato ricevuto dal browser client;
- l'obiettivo del plugin [client] è anche quello di iniettare una funzione [$session] nel contesto client. Questa funzione restituirà la sessione inviata dal server;
- riga 19: la funzione inserita [$session] restituirà la sessione delle righe 9–16;
- Righe 9–16: l'oggetto [session] gestito dal client. Si tratterà di una copia della sessione inviata dal server;
- riga 11: il valore della sessione client viene recuperato dal cookie di sessione inviato dal server [nuxt];
- righe 13–15: come per la sessione del server, la sessione del client dispone di una funzione [save] che consente di salvare il valore della sessione, [this.value] alla riga 14, nel cookie di sessione memorizzato nel browser;
9.6. La pagina [index]
La pagina [index] si evolve come segue:
<!-- 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>
Tieni presente che questa pagina viene eseguita sia sul server che sul client.
- Riga 8: Ora visualizziamo sia la sessione che lo store;
- riga 30: [jsonSession] è una proprietà calcolata che restituisce la stringa JSON del valore della sessione;
- riga 41: visualizziamo il valore della sessione utilizzando la funzione iniettata [this.$session]. Questa esiste sia nel contesto del server che in quello del client;
- riga 53: il metodo [incrementCounter] viene eseguito solo sul lato client;
- Riga 56: Il contatore cieco viene incrementato e visualizzato come prima;
- riga 58: la sessione viene recuperata utilizzando la funzione iniettata [this.$session];
- riga 59: l'archivio della sessione viene aggiornato;
- righe 60–61: incrementiamo gli attributi della sessione [somethingImportant.x, somethingImportant.y]. Questo serve solo a mostrare che una sessione può essere utilizzata per trasportare dati diversi dallo store;
- riga 63: la sessione viene salvata nel cookie di sessione memorizzato nel browser. In una vista client, il contesto della sessione è disponibile in [this.$nuxt.context];
Lo scopo della pagina [index] è dimostrare che la sessione non è reattiva, mentre lo store lo è. Quando incrementiamo gli elementi della sessione, vedremo che la vista non viene aggiornata. La vista [page1] presenta una soluzione a questo problema.
9.7. La pagina [page1]
La pagina [page1] viene creata copiando la pagina [index] e modificandola leggermente:
<!-- 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>
- riga 47: la differenza principale è che stiamo aggiungendo la sessione corrente alle proprietà della pagina (righe 29–33). Questo renderà la sessione reattiva. Quando la funzione [incrementCounter] incrementa gli elementi della sessione, la vista [page1] verrà aggiornata;
9.8. Esecuzione del progetto
Prima di eseguire il progetto, controlla il cookie di sessione del tuo browser e, se esiste, cancellalo in modo che il server crei una nuova sessione:

Ora richiediamo l'URL [http://localhost:81/nuxt-06/]:

I log del browser sono quindi i seguenti:

- In [2], il server avvia una nuova sessione nel plugin [session] del server;
- in [3], questa nuova sessione viene inizializzata in [nuxtServerInit];
- in [4], la nuova sessione così come è nota sul server;
- in [5], il client ha recuperato con successo questa sessione;
Ora incrementiamo il contatore di tre volte:

- in [3], il contatore è stato incrementato ma non la sessione in [2]. Mentre [3] mostra lo store, che è reattivo, [2] mostra la sessione, che non è reattiva:
Ora ricarichiamo la pagina (F5). Dopo questa ricarica, i log sono i seguenti:

- In [2], vediamo che il server ha ricevuto un cookie di sessione inviato dal browser del client;
- in [4], vediamo che lo store non viene azzerato ma viene riportato dalla sessione ricevuta;
- in [4-5]: vediamo che gli attributi di sessione sono stati effettivamente tutti incrementati tre volte;
La pagina inviata dal server è quindi la seguente;

La conclusione che si può trarre da questa pagina è che la sessione può contenere elementi diversi dallo store, ma questi non sono reattivi.
Ora clicchiamo sul link [Pagina 1] [4]. La nuova pagina visualizzata è la seguente:

Quindi utilizziamo il pulsante [Incrementa] tre volte. La pagina diventa la seguente:

Questa volta, la sessione viene visualizzata correttamente in [2]. Qui è reattiva. Lo si può vedere nei log:

- in [1-3], i valori della sessione;
- in [4-6], i getter e i setter reattivi per gli elementi della sessione;
Ora clicchiamo sul link [Home] [4]. Otteniamo la seguente pagina:

Quindi clicchiamo due volte sul pulsante [Increment] [4]. La pagina cambia come segue:

Possiamo notare che anche qui la sessione è diventata reattiva [2].
Recuperiamo il valore restituito dalla funzione [this.$session()]:

- Nella scheda [View], seleziona la pagina corrente [Home] per ottenere il suo riferimento [$vm0] [3];
Quindi, nella scheda [Console] [4], recuperiamo il valore della funzione [$vm0.$session()]:

- in [5], vediamo che la sessione è diventata reattiva, mentre inizialmente non lo era;
- in [6], richiediamo il valore della sessione;
- in [7-8], scopriamo che anche questo valore è diventato reattivo;
Abbiamo quindi un risultato inaspettato: se un elemento diventa reattivo in una pagina perché è stato inserito nelle proprietà della pagina, allora diventa reattivo anche nelle pagine in cui non fa parte delle proprietà.
9.9. Conclusione
L'esempio [nuxt-05] ha mostrato che è possibile mantenere lo store tra le richieste effettuate al server. L'esempio [nuxt-06] fa la stessa cosa con un oggetto che abbiamo chiamato [session] per analogia con la sessione web. Abbiamo visto che questa sessione poteva avere le stesse proprietà dello store [Vuex] e diventare anch'essa reattiva, anche se non lo era in modo nativo.
Allora, qual è lo scopo dello store [Vuex]? Devo ammettere che, per ora, non mi è ancora chiaro. È probabile che mi sia sfuggito qualcosa. Quindi, in caso di dubbio, consiglierei di utilizzare:
- uno store [Vuex] per contenere tutto ciò che deve essere condiviso tra le pagine lato client e tutto ciò che potrebbe dover essere condiviso tra il client e il server;
- un cookie di sessione se lo store deve essere mantenuto durante una richiesta client-server, con la sessione contenente solo lo store;
Gli esempi [nuxt-05] e [nuxt-06] avevano lo scopo di mostrare come garantire la continuità dell'applicazione quando l'utente forza una chiamata al server digitando manualmente gli URL. Si noti che il comportamento predefinito in questo caso è un riavvio dell'applicazione, con conseguente perdita dello stato corrente.