8. 示例 [nuxt-05]:利用会话 Cookie 保持存储状态
目标:希望存储 [Vuex] 不会在每次向服务器发送请求时被重置。为此,我们将使用一个会话 Cookie:
- 存储将由服务器初始化,并由服务器将其放入会话cookie中;
- 客户端浏览器将接收该会话cookie,并在每次向服务器发送新请求时自动将其发送出去;
- 服务器随后可读取该会话cookie,并使用其中包含的存储数据进行处理,该存储数据由客户端实时更新;
8.1. Présentation
项目 [nuxt-05] 最初是通过复制项目 [nuxt-04] 获得的:

我们将看到,只有文件 [store / index.js] 会发生变化。
要在 [nuxt] 中使用 Cookie,我们将使用模块 [cookie-universal-nuxt],该模块随 [yarn] 一起安装在终端 VSCode 中:

- 在 [4] 中,输入命令 [yarn add cookie-universal-nuxt];
这样,项目 [dvp] 的文件 [package.json] 中便新增了一个模块:
...
},
"dependencies": {
"@nuxtjs/axios": "^5.3.6",
"bootstrap": "^4.1.3",
"bootstrap-vue": "^2.0.0",
"cookie-universal-nuxt": "^2.0.19",
"nuxt": "^2.0.0"
},
8.2. 配置文件 [nuxt.config.js]
为了使 [nuxt] 能够使用 [cookie-universal-nuxt] 的 Cookie,需要在配置文件 [nuxt.config.js] 中声明该模块:
...
],
/*
** Nuxt.js modules
*/
modules: [
// 文档:https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// 文档:https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
...
- 第 12 行,将模块 [cookie-universal-nuxt] 添加到 [6] 的模块数组中(该数组属于 [nuxt]);
最终文件 [nuxt.config.js] 如下所示:
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'Introduction à [nuxt.js]',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'ssr routing loading asyncdata middleware plugins store'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// 文档:https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// 文档:https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// 文档:https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
// 源代码目录
srcDir: 'nuxt-05',
// 路由器
router: {
// 应用程序的 URL 根目录
base: '/nuxt-05/'
},
// 服务器
server: {
// 服务端口,默认 3000
port: 81,
// 监听的网络地址,默认 localhost:127.0.0.1
// 0.0.0.0 = 该机器的所有网络地址
host: 'localhost'
},
// 环境
env: {
maxAge: 60 * 5
}
}
- 第 79 行:已向文件中添加了键 [env]。该键为保留字。在此对象中声明的元素可通过应用程序元素中的 [context.env] 对象访问;
- 第 80 行:[maxAge] 属性将作为会话 Cookie 的最大生命周期,该时长从 Cookie 最后一次初始化时开始计算。该时长以秒为单位。此处设置的生命周期为 5 分钟;
8.3. 存储持久化的原理
客户端与服务器之间交换的 Cookie 在双方(客户端和服务器)均可通过以下路径访问:
- [context.app.$cookies](即对象 [context] 可用之处,也就是几乎所有地方);
- [this.$cookies](位于某个视图内部);
使用表达式 [...$cookies.get(‘nom_du_cookie’)] 可获取特定 Cookie。使用表达式 [...$cookies.set(‘nom_du_cookie’, valeur_du_cookie)] 可设置 Cookie 的值。
存储持久化 Cookie 的原理如下:
- 当服务器在函数 [nuxtServerInit] 中初始化存储时,存储的状态将保存在名为“session”的 Cookie 中;
- 该“session”Cookie将作为服务器响应HTTP的一部分返回。众所周知,浏览器会将服务器发送的Cookie回传给服务器,且在每次向服务器发起新请求时都会如此。同样,服务器会在发送给客户端的页面中包含存储区;
- 在浏览器中,客户端应用程序会获取服务器发送的存储数据,并据此执行后续操作。我们将确保每次修改存储数据时,其新状态都会被存储在浏览器记录的“session”Cookie中;
- 如果用户强制向服务器发起请求,客户端浏览器将自动返回服务器先前发送的所有cookie,特别是名为“session”的cookie;
- 当服务器在响应此请求后重新初始化数据存储时,它将获取名为“session”的Cookie,并使用其中的值来初始化数据存储的状态;
- 因此,客户端与服务器之间的存储状态将保持连续性;
8.4. 存储的初始化
存储器在文件 [store / index.js] 中实现:
/* eslint-disable no-console */
export const state = () => ({
// 计数器
counter: 0
})
export const mutations = {
// 计数器递增一个值 [inc]
increment(state, inc) {
state.counter += inc
},
// 状态替换
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// 谁在执行这段代码?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// 等待一个 Promise 完成
await new Promise(function(resolve, reject) {
// 此处通常有一个异步函数
// 通过等待一秒来模拟它
setTimeout(() => {
// 初始化会话
initStore(store, context)
// 成功
resolve()
}, 1000)
})
}
}
function initStore(store, context) {
// 当前请求中是否存在会话cookie
const cookies = context.app.$cookies
const session = cookies.get('session')
if (!session) {
// 不存在会话
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// 初始化存储
store.commit('increment', 77)
} else {
console.log("nuxtServerInit, reprise d'une session existante")
// 使用会话cookie更新存储
store.commit('replace', session.store)
}
// 将存储器写入会话cookie
cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
// 日志
console.log('initStore terminé, store=', store.state)
}
注释
- 第2-5行:存储区将由一个计数器组成;
- 第 9-11 行:该计数器可以递增;
- 第13-17行:存储器的状态可从新状态初始化。此功能旨在展示当存储器不限于此处的计数器时,其可能的初始化方式;
- 第21-35行:函数[nuxtServerInit]未作更改;
- 第 30 行:当一秒的等待时间结束时,使用第 38-56 行中的函数初始化存储器;
- 第40-41行:首先获取名为“session”的Cookie:
- 在应用程序首次运行时以及向服务器发送的第一个请求中,该 Cookie 尚不存在。此时将创建该 Cookie(第 53 行)并将其发送至客户端浏览器;
- 在同一应用程序运行期间,当向服务器发出第2、第3……次请求时,该Cookie将存在,因为客户端浏览器会在每次向服务器发出新请求时将其一并发送;
- 在第二次运行应用程序并向服务器发出第一个请求时,该 Cookie 也可能存在。实际上,在步骤 1 完成后,Cookie 已被存储在浏览器中,并具有一定的有效期。如果未超过该有效期,名为“session”的 Cookie 将随向服务器发出的第一个请求一并发送
简而言之,对于每次向服务器发出的请求:如果“session”Cookie已存储在客户端浏览器中,则服务器将收到它;否则,服务器将无法收到它。
- 第42-47行:如果服务器未收到会话Cookie,则第46行将初始化存储;
- 随后在第53行,将创建一个名为“session”的Cookie并将其放入服务器的HTTP响应中。该Cookie的值为对象[{ store: store.state }]。因此,放入会话Cookie中的是存储器的状态,而非存储器本身;
- 函数 [set] 的第三个参数是一个选项对象:
- [path] 指定该 Cookie 应返回给哪个 URL。 [context.base] 是应用程序 [nuxt-05] 的基础 URL。该基础对象在文件 [nuxt.config.js] 中定义:
// 路由器
router: {
// 应用程序的 URL 根目录
base: '/nuxt-05/'
},
- [maxAge] 是浏览器中 Cookie 的有效期(以秒为单位)。超过此时间后,浏览器将不再将其发回服务器。[context.env.maxAge] 同样返回了 [nuxt.config.js] 文件中记录的值:
[env] 是配置文件中的保留关键字。此处将有效期设置为 5 分钟。该时长以浏览器上次接收会话 Cookie 时为基准进行计算。超过此时间后,Cookie 将不再发回给服务器,服务器届时需启动新的会话;
- 第 48-50 行:如果服务器收到会话 Cookie,则使用该 Cookie 中的 [store] 对象初始化存储状态。需要注意的是,该对象包含存储的保存状态;
- 随后在第53行,会将会话cookie放入发给客户端浏览器的响应中:
- 函数 [get] 将从服务器接收的请求中提取会话 Cookie;
- 函数 [set] 将会话 Cookie 放入服务器发给客户端浏览器的响应中;
- 随后在第53行,会将会话cookie放入发给客户端浏览器的响应中:
8.5. 商店计数器的递增
[index.vue] 页面中的计数器递增过程如下:
// 事件管理
methods: {
incrementCounter() {
console.log('incrementCounter')
// 计数器递增 1
this.$store.commit('increment', 1)
// 更改显示值
this.value = this.$store.state.counter
// 将存储信息保存到会话cookie中
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
在客户端,每次修改存储区时,都必须将其保存到会话cookie中。因为用户随时可能手动请求 URL,此时必须能够向服务器发送最新的存储区。 因此,在第 10 行,在增量存储计数器之后,我们将存储的状态保存到会话 Cookie 中:
- Cookie 存储在 [this.$cookies] 属性中;
- 存储器 [this.$store.state] 的状态保存在与键 [store] 关联的 Cookie 中;
- Cookie的路径为[context.base]。在视图中,上下文可通过[this.$nuxt.context]获取;
- Cookie 的生命周期为 [context.env.maxAge],此处可在属性 [this.$nuxt.context.env.maxAge] 中获取;
8.6. 运行示例 [nuxt-05]
我们启动应用程序 [nuxt-05]:

以下截图来自Chrome浏览器。我们请求 URL [http://localhost:81/nuxt-05/]。请务必在 /nuxt-05 后面添加最后一个 /,否则将无法获得预期结果:

- 在 [4] 中,我们获得了存储器的初始值(77);
让我们查看浏览器日志(F12):

- 在 [5-6] 中,是服务器的日志;
- 在 [7] 中,可以看到服务器启动了一个新会话。这意味着它未收到会话 Cookie;
- 在 [8] 中,计数器初始化为 77;
- 在 [9] 中,服务器(9)和客户端(10)的 [index] 页面确实显示了相同的计数器值;
现在来看浏览器接收到的cookie:

- 在 [1] 中,选择 [Application] 标签页,然后选择 [Cookies] [2] 选项。 在浏览器的所有Cookie中,选择来自[http://localhost:81]域的Cookie;
- 在 [4] 中,找到名为“session”的 Cookie。若未找到,请刷新页面 [F5]:可能是该 Cookie 的 5 分钟有效期已过;
- 在 [5] 中,是 Cookie 的值。尽管由于字符编码 { : 的影响,内容不太清晰,但仍可辨认出计数器的值 77;
- 在 [6] 中,是该 Cookie 的 URL 部分:每当请求该 URL 时,浏览器都会将 Cookie 发送给服务器;
- 在 [7] 中,表示 Cookie 的过期时间。当超过该时间后,浏览器将销毁该 Cookie;
请确保拥有该 Cookie。若未拥有,请刷新页面(F5)。当页面包含该 Cookie 时,请再次刷新页面(F5)。此时日志将变为如下内容:

这次在 [3] 中,服务器确实成功获取了会话 Cookie。这是客户端浏览器发送给服务器的。
现在,请逐步增加计数器值,然后不时刷新当前页面(F5),无论是[index]还是[page1], 你会发现计数器并未像示例 [nuxt-04] 那样重置为 77,而是保留了页面刷新前客户端浏览器中的原始值:


此时浏览器的日志如下:

注:在测试过程中,您可能需要删除存储在浏览器中的会话 Cookie [5],以便在下次向服务器发送请求时,由服务器初始化一个新的会话。
最后,让我们展示[index]页面中的[incrementCounter]函数对存储在客户端浏览器中的会话Cookie的影响:
// 事件管理
methods: {
incrementCounter() {
console.log('incrementCounter')
// 计数器加1
this.$store.commit('increment', 1)
// 更改显示值
this.value = this.$store.state.counter
// 将存储值保存到会话cookie中
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
- 第 10 行:计数器的修改会反映在会话 Cookie 中;
让我们验证这一点。我们从以下情况开始:

- 在 [4] 时,会话 Cookie 的计数器确实反映了显示的值 [1];
现在,我们将计数器递增一次,变为 [5]。会话 Cookie 从 [4] 演变为如下形式:

- 变为 [7],会话 Cookie 的计数器确实已变为 84。要查看这一点,需要刷新视图 [8]。 操作方法:先在 [Storage] 中选择 [9] 选项,然后重新选择 [8] 选项。此时,会话 Cookie 的新值应会显示出来;