本篇主要介绍基于 NuxtJS 构建的 vue 后端渲染(SSR)项目中对于 vuex 的使用与状态持久化方面的介绍。有不足之处或是任何意见建议,欢迎各位大佬不吝斧正~
vuex 的使用
在一般的 vue 项目中使用 vuex,我们一般都是得先 import
vuex 再 use
, 最后再 new Vuex.Store({})
进行配置。而在 nuxt 中则无需这些步骤,因为当新建项目完成后,你会发现目录中已经自动生成了 store 目录,只要在其中新建 index.js,就可以开始使用 vuex 了,因为 Nuxt.js 内核就实现了 Vuex。本项目中,采用了 nuxt 官方推荐使用的模块化方式对 vuex 进行使用:
模块化定义
采用模块的方式,在 store 目录下新建 index.js 作为根模块并导出 state、mutations、actions 和 getters。注意在 nuxt 项目中,state 需要被定义为函数的形式并返回一个对象,其它 3 个则是直接定义成对象。
// state 需要定义成函数
export const state = () => ({
tenant: ''
})
在 store 目录下,每定义一个新的 .js 文件,都是一个模块,比如我们可以再新建一个 user.js 文件,用于存放用户登录后得到的 token:
// store\user.js
export const state = () => ({
token: ''
})
export const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
}
}
export const actions = {
// 账号密码登录
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
clientSetting.username = username.trim()
clientSetting.password = password
// 调接口
this.$api.passport
.login(clientSetting)
.then(res => {
// 接口返回 token 并保存
commit('SET_TOKEN', res.access_token)
resolve(res)
})
.catch(error => {
reject(error)
})
})
}
}
export const getters = {
isLogin: state => !!state.token
}
在 Vue Devtools 中查看,可以发现子模块中定义的 state 会被放在 user 对象下,getters 前面则有个 user/
在页面(组件)中使用
与一般的 vue 项目中操作 vuex 没什么区别,例如想要触发上面定义在 store\user.js 中 mutation 里的SET_TOKEN
,则可以直接通过如下方式触发:
this.$store.commit('user/SET_TOKEN', res.access_token)
注意因为是子模块,所以方法名前加了个 user/
。
使用辅助函数
同样也可以使用辅助函数让我们少敲几个代码,这些辅助函数返回值都是对象,所以可以用解构赋值的方法直接添加到 computed
或是 methods
:
import { mapState } from 'vuex'
computed: {
...mapState({
token: state => state.user.token
})
}
当然还有 mapGetters
, mapMutations
和 mapActions
,因使用方式与一般 vue 项目无异,故不在此赘述。
状态持久化
因为 vuex 里的数据都是存在内存里的,所以如果不做状态持久化的处理,只要刷新页面,state 里的数据就会恢复到初始状态。在一般 vue 项目中,我们通常会借助 localStorage、sessionStorage 或是 cookies 来做状态的持久化,于是当我准备在 nuxt 项目中直接使用 sessionStorage 时,虽然经过一番折腾表面看起来代码也能运行没什么问题,但发现浏览器报错了,如下图所示:
我猜测这可能与 nuxt 属于同构框架有关,服务器端不能直接使用属于 window
对象的 sessionStorage
。解决的办法目前在本项目中有 2 种,分别是使用第三方封装好的包来实现在 nuxt 中对 cookie 和 Web Storage 的使用:
1. cookie-universal-nuxt
借用 cookie 来实现状态持久化,需要安装一个包 npm i cookie-universal-nuxt
,安装完后在 nuxt.config.js 进行配置:
modules: [ 'cookie-universal-nuxt' ],
这样我们就能在 nuxt 上下文里拿到 $cookies
对象了。在本项目中的实际应用之一是对登录后从服务器获取的 token 进行状态持久化处理。一共可以分为 3 部分:
1). 登录时:
登录时同步 vuex 和 cookies,在 login.vue 登录页面,用户填写好账号密码按下登录按钮,则 dispatch
上文中写在 store\user.js 里的关于登录的 action,该 action 返回一个 Promise 对象,一旦登录成功获取到 token,则进行 resolve(res)
,那么我们就能在 login 页面通过 .then
的回调获取,并保存到 cookies 里:
handleLogin() {
this.$store
.dispatch('user/login', this.loginForm) // 将登录表单数据传给 vuex 处理
.then(res => {
this.$cookies.set('token', res.access_token) // 登录成功,则将 token 保存到 cookies 中
// ...
})
},
2). 强刷页面后
强刷页面后,vuex 中存储的 token 会被初始化回初状态,此时我们可以在 nuxtServerInit
这一 nuxt 特有的生命周期中通过 cookies 得到 token 再重新 commit
,将值赋给 state
中的 token。这里顺便介绍下 nuxtServerInit
,该函数仅在服务器端渲染中运行一次,并且只能定义在 store 的主模板当中的 actions
对象里:
// store\index.js
export const actions = {
nuxtServerInit(store, { app: { $cookies } }) {
// ...
// 初始化相关数据存到 store 中
const token = $cookies.get('token') || ''
store.commit('user/SET_TOKEN', token)
}
}
nuxtServerInit
有两个参数,第一个是 vuex 上下文对象,也就是 store
;第二个是 nuxt 上下文对象,我们可以在里面解构得到 app
对象,并再次解构得到 $cookies
。
3). axios 请求拦截中
读取 vuex 数据给请求头添加 token,因为读取 vuex 是从内存读取的,相较于从磁盘读取 cookies 更加节省性能:
// plugins\axios.js
service.onRequest(config => {
if (store.state.user.token) {
config.headers.authorization = 'Bearer ' + store.state.user.token
}
// ...
})
2. nuxt-vuex-localstorage
我们都知道 cookies 会随着请求头每次提交,如果后端并不需要这些存储在 cookies 中的信息,无疑会对流量造成一些损失;另外 cookies 还有大小的限制,为 4kb 左右。所以项目中还用到了另一包:nuxt-vuex-localstorage,用于通过 Web Storage 来对 vuex 的数据做状态持久化:
1). 下载安装 nuxt-vuex-localstorage
npm i nuxt-vuex-localstorage
2). 在 nuxt.config.js 配置
modules: [
// ...
[
'nuxt-vuex-localstorage',
{
localStorage: ['lsSubmitTranshipOrder']
}
]
],
在 store 目录下新建 lsSubmitTranshipOrder.js(文件名是自定义的),里面定义的 state 就会被存储到 localStorage。
3). 在 store 目录下新建 lsSubmitTranshipOrder.js 文件
// store\lsSubmitTranshipOrder.js
export const state = () => ({
orderMsg: {
orderItemId: '', // 字符串
addedServiceFee: [], // 数组对象
expire: 12 // 过期时间为 12 小时
}
})
export const mutations = {
SET_ORDER_ITEM_ID: (state, orderItemId) => {
state.orderMsg.orderItemId = orderItemId
},
SET_ADDED_SERVICE_FEE: (state, feeObjArr) => {
state.orderMsg.addedServiceFee = feeObjArr
}
}
// ...
nuxt-vuex-localstorage 还可以通过 expire
对 localstorage 设置过期时间。
4). 使用
现在我们就可以直接在页面中使用 state 里的数据了,即使刷新页面,store\lsSubmitTranshipOrder.js 中定义的 state 也不会被初始化,而是会自动从 localStorage 中重新取值。而且即使是存储非字符串类型的数据,如数组或对象,亦无需做数据格式的转换而可直接使用~