用 NuxtJS 构建 SSR 商城 实战笔记 —— vuex 的使用与状态持久化

2,266 阅读4分钟

本篇主要介绍基于 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/

image.png

在页面(组件)中使用

与一般的 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, mapMutationsmapActions,因使用方式与一般 vue 项目无异,故不在此赘述。

状态持久化

因为 vuex 里的数据都是存在内存里的,所以如果不做状态持久化的处理,只要刷新页面,state 里的数据就会恢复到初始状态。在一般 vue 项目中,我们通常会借助 localStorage、sessionStorage 或是 cookies 来做状态的持久化,于是当我准备在 nuxt 项目中直接使用 sessionStorage 时,虽然经过一番折腾表面看起来代码也能运行没什么问题,但发现浏览器报错了,如下图所示:

2021-08-19_110224.png
我猜测这可能与 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 中重新取值。而且即使是存储非字符串类型的数据,如数组或对象,亦无需做数据格式的转换而可直接使用~

感谢.gif
点赞.png