放几个阿里云的优惠链接 代金券 / 高性能服务器2折起 / 高性能服务器5折
上一篇将了如何项目的基本结构和如何创建。本文讲下服务端渲染的数据同步与Auth的Token问题。
本文额外使用cookie-universal-nuxt模块来快捷处理cookie。
Nuxt集成了Vuex作为数据共享的组件,所以启用Vuex也非常简单。直接在store目录下新建index.js文件,创建Vuex有些区别,不推荐使用**export default new Vuex.Store({})**这种方式。官网给了例子,只需要直接export state, mutations, actions这些即可。Nuxt还提供了nuxtServerInit方法用户服务端初始化时数据的处理。其他的调用方式基本和客户端渲染时一样。
export const state = () => ({
token: null
})
export const mutations = {
setToken(state, token) {
state.token = token
}
}
由于Nuxt服务端不存在localStorage这些在window上的对象,所以不能使用这些在服务端上存数数据。因此需要通过Vuex将数据进行同步。
其实服务端可以实现自己的数据存储方式,毕竟大部分情况下Nuxt都是部署在Node服务器下的,完全可以通过Node的数据存储方案来实现。
虽然Nuxt给出了一个auth-module用于做身份验证,但是对于简单的应用来说并完全可以通过Vuex来自己实现相应的功能。在上面已经设置了token属性,下面来演示如何使用。
首先前端的token采用存储在cookie上面,这样每次请求时会自动发送token。
添加nuxtServerInit来获取请求中的token。当不存在对应的cookie时则认为当前没有通过身份验证。相应的页面显示和逻辑自行实现。
export const actions = {
nuxtServerInit({commit}, {app}) {
let token = app.$cookies.get('token')
commit('setToken', token)
}
}
创建auth.js中间件检测token
export default function ({ store, redirect }) {
// 如果用户未经过身份验证
if (!store.state.token) {
return redirect('/login')
}
}
在nuxt.config.js中配置中间件,也可以针对单独页面配置。
router: {
middleware: 'auth'
},
这样当没有登陆的用户访问时会被重定向到login页面进行登陆。当登陆成功后将token写入cookie即可实现一个简单的身份认证功能(需要判断process.browser来确认是否是浏览器,只有浏览器才有cookie保存的功能)。
上面虽然实现了数据的共享,但是对于一些数据每次都需要判断是否是浏览器环境显的有些繁琐,下面来实现一个插件来免去这些判断。
定义一些基本的方法。
export class DataStore {
setItem(key, value) {
}
getItem(key) {
}
getKeys() {
}
removeItem(key) {
}
clear() {
}
}
import Cookies from 'js-cookie'
export class CookieStore extends DataStore {
name = 'CookieStore'
prefix = ''
_keys = '__cookieds_keys__'
expires = 365
constructor(prefix = 'ds', expires = 365) {
super()
this.prefix = prefix
this._keys = `${prefix}__cookieds_keys__`
this.expires = expires
}
setItem(key, value) {
Cookies.set(key, JSON.stringify(value), {expires: this.expires})
let keys = this.getKeys()
if (keys.findIndex(k => k === key) >= 0) {
return
}
localStorage.setItem(this._keys, JSON.stringify([...keys, key]))
}
getItem(key) {
const v = Cookies.get(key)
if (v === undefined) {
return undefined
}
return JSON.parse(v)
}
getKeys() {
const v = localStorage.getItem(this._keys)
if (v === undefined || v === null) {
return []
}
return JSON.parse(v)
}
removeItem(key) {
Cookies.remove(key)
let keys = this.getKeys()
keys = keys.filter(k => k !== key)
localStorage.setItem(this._keys, JSON.stringify(keys))
}
clear() {
let keys = this.getKeys()
keys.forEach(k => {
Cookies.remove(k)
}
)
localStorage.removeItem(this._keys)
}
}
export class VuexStore extends DataStore {
vuex = null
name = 'VuexStore'
moduleName = null
constructor(vuex, moduleName = '__vuex_store__') {
super()
this.vuex = vuex
this.moduleName = moduleName
// 数据同步时只会同步state导致模块未同步,这里用 _mutations 来判断重复注册
if (vuex._mutations[moduleName + '/__updateStore__']) {
return
}
vuex.registerModule(moduleName, {
state: {
map: {}
},
namespaced: true,
mutations: {
__updateStore__(state, value) {
state.map = value
}
}
})
}
setItem(key, value) {
const map = Object.assign({}, this.vuex.state[this.moduleName].map)
map[key] = value
this.vuex.commit(this.moduleName + '/__updateStore__', map)
}
getItem(key) {
const v = this.vuex.state[this.moduleName].map[key]
if (typeof v === 'object') {
return Object.assign({}, v)
}
return v
}
getKeys() {
return Object.keys(this.vuex.state[this.moduleName].map)
}
removeItem(key) {
const map = Object.assign({}, this.vuex.state[this.moduleName].map)
delete map[key]
this.vuex.commit(this.moduleName + '/__updateStore__', map)
}
clear() {
this.vuex.commit(this.moduleName + '/__updateStore__', {})
}
}
export class MixStore extends DataStore {
stores = []
name = 'MixStore'
constructor(stores = []) {
super()
this.stores = stores
}
setItem(key, value) {
this.stores.forEach(s => {
if (s.test) {
if (s.test(key, value)) {
s.setItem(key, value)
}
} else {
s.setItem(key, value)
}
})
}
getItem(key) {
for (let i = 0; i < this.stores.length; i++) {
const s = this.stores[i]
if (s.test) {
if (s.test(key)) {
const v = s.getItem(key)
if (v !== undefined) {
return v
}
}
} else {
const v = s.getItem(key)
if (v !== undefined) {
return v
}
}
}
}
getKeys() {
let keys = []
this.stores.forEach(s => {
keys = [...keys, s.getKeys()]
})
const tmp = {}
keys.forEach(key => {
tmp[key] = 0
})
return Object.keys(tmp)
}
removeItem(key) {
this.stores.forEach(s => {
s.removeItem(key)
})
}
clear() {
this.stores.forEach(s => {
s.clear()
})
}
}
在plugin下面创建ds.js,根据不同的环境创建不通的存储实例即可。
import Vue from 'vue'
/**
* 初始化全局公用实例,前后端同步
* 同步采样Vuex,因为这个是自动同步的
* 客户端在Vuex之后在添加其他数据存储策略
*/
export default (ctx) => {
let store = null
if (process.browser) {
const stores = []
stores.push(new VuexStore(ctx.store))
stores.push(new CookieStore())
store = new MixStore(stores)
} else {
store = new VuexStore(ctx.store)
}
Vue.prototype.$ds = store
ctx.$ds = store
ctx.store.$ds = store
}
配置nuxt.config.js
plugins: [
...
'@/plugins/api',
],
在使用时只需要调用this.$ds即可获取相应对象。
Nuxt服务端渲染中存在的最大问题便是数据如何同步,虽然官方给出了Vuex作为解决方案,但是要同时处理客户端与服务端中的差异还是存在一些问题,当项目复杂之后还需要考虑数据缓存等问题。尤其时那些直接从单页面项目直接迁移过来的(比如说我),迁移中会有很多的坑需要填(这些以后在讲了)。