mmf-blog-vue2-ssr
mmf-blog-vue2-ssr copied to clipboard
做服务端渲染时,只保存了一份cookie,那多个用户同时请求时好像会交叉请求状态污染
看了一下代码,先是在entry-server.js
中调了api.setCookies(context.cookies)
,然后在this.api
中就已经设置好cookie
了,所有请求使用的都是同一份index-server.js
导出的实例,这样在多个用户同时请求时,如A先请求,先设置了A的cookie,A的请求还没处理完成,B也来请求了,这样不是B的cookie会覆盖掉A的cookie吗,这样后续A的请求不是就会被污染了吗?如果分析有误,还请指正!
确实是这样, 从vue2.3开始, runInNewContext 建议设置成 false 后, ssr 的cookies就是个问题, 服务端每个用户都共享一个上下文, 导致cookies不能存在服务端给api调用, 我这边也没什么好的思路, 目前可能把代码改回去, 还是得在asyncData, 把cookies传到api里去...
要是有什么好的想法, 也可以说说, 一起讨论下.
刚想到一种方法,就是像创建app、router、store一样去创建axios,但是axios又没必要为每一个连接就axios.create()一个实例,那就共享一个axios.create()的实例就好了,只是为每一个连接创建一部分私有的东西,然后在里面共享同一个axios.create()出来的实例,通过export default CreateAxios 出来一个类,在创建store的时候传入store进行初始化CreateAxios,把store和CreateAxios的实例进行绑定,这样通过CreateAxios实例去发起请求就可以拿到对应连接的store中的数据了,文章写的太烂了,还是看代码
以下代码是在nuxt.js中的实现
一、axios封装,导出一个CreateAxios
// ./utils/index.js
import originAxios from 'axios'
import qs from 'qs'
import Api from '../api'
const axios = originAxios.create({
baseURL: 'https://cnodejs.org/api/v1',
headers: {
post: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
})
// 响应拦截器
axios.interceptors.response.use(response => {
return response && response.data
}, err => {
// 错误处理, 处理success为false的情况
if (err.response && err.response.data) {
return Promise.resolve(err.response.data)
}
return Promise.reject(err)
})
class CreateAxios extends Api {
constructor(store) {
super(store)
this.store = store
}
getAccessToken() {
return this.store.state.accessToken
}
get(url, config = {}) {
let accessToken = this.getAccessToken()
config.params = config.params || {}
// 变量是accessToken 做参数时全小写accesstoken
accessToken && (config.params.accesstoken = accessToken)
return axios.get(url, config)
}
post(url, data = {}, config = {}) {
let accessToken = this.getAccessToken()
accessToken && (data.accesstoken = accessToken)
return axios.post(url, qs.stringify(data), config)
}
// 实例保存在store.$axios上,返回服务端渲染结果时会用JSON.stringify对store处理
// 添加toJSON绕过JSON.stringify
toJSON() {}
}
export default CreateAxios
至于extends的Api只是我封装的一些请求
// ./api/index.js
class Api {
get() {
throw new Error('Abstract methods must be implemented')
}
post() {
throw new Error('Abstract methods must be implemented')
}
// 获取主题列表
getTopics(page = 1, tab = 'all', limit = 40, mdrender = 'false') {
return this.get('/topics', {
params: {
page,
tab,
limit,
mdrender
}
})
}
// ...
}
export default Api
二、在store工厂函数中创建CreateAxios的实例,传入store进行绑定
// ./store/index.js
import CreateAxios from '../utils/axios'
Vue.use(Vuex)
const store = () => {
let store = new Vuex.Store({
state,
getters,
mutations,
actions
})
store.$axios = store.state.$axios = new CreateAxios(store)
return store
}
export default store
这样在创建store的同时,就和CreateAxios的实例绑定好了,在组件内就可以通过store.$axios或this.$store.$axios去发起请求,在action中可以通过state.$axios或rootState.axios发起请求(仅限服务端渲染时,因为在客户端store.replaceState(window.INITIAL_STATE)把state.$axios给覆盖了),暂时还没有好的方法,
为了在组件内调用方便些,可以通过this.$axios调用,就用这样一个全局mixin去处理了,不过也遇到一些问题,原因未明,不过在页面级
的组件里面都是可以访问到的,从beforeCreate
就可以
// ./mixin/index.js
export const axiosMixin = {
beforeCreate() {
// 原因未知,前面有几个组件beforeCreate的this.$store为undefined
if (this.$store && this.$store.$axios) {
this.$axios = this.$store.$axios
}
}
}
三、cookie的共享,在store的nuxtServerInit(只会在服务端渲染时执行)中获取cookie,把cookie中的accessToken保存到store,然后CreateAxios的实例就可以通过store拿到对应的accessToken了
// ./store/actions.js
export const nuxtServerInit = async ({ commit, dispatch, state }, { req }) => {
let accessToken = parseCookieByName(req.headers.cookie, 'access_token')
if (!!accessToken) {
try {
let res = await state.$axios.checkAccesstoken(accessToken)
if (res.success) {
let userDetail = await state.$axios.getUserDetail(res.loginname)
userDetail.data.id = res.id
// 提交登录状态及用户信息
dispatch('setUserInfo', {
loginState: true,
user: userDetail.data,
accessToken: accessToken
})
}
} catch (e) {
console.log('fail in nuxtServerInit', e.message)
}
}
}
总结
对比前后,axios请求由之前可以在任何地方发起,变成只能在asyncData、组件内及action中(服务端渲染时)请求,因为是基于nuxt的项目,好像有些地方没有vue ssr好定制,不过cookie的问题好像可以解决了,还有很多不足,以及可以如何优化和改进,也不知道有没有什么潜在的问题,还请多指点了,要是有其他好的方法,欢迎分享! github地址 线上地址
应该这样就可以在客户端,在action中使用state.$axios或rootState.$axios发请求了
if (process.browser) {
let replaceState = store.replaceState.bind(store)
store.replaceState = (...args) => {
replaceState(...args)
store.state.$axios = store.$axios
replaceState = null
}
}
昨天测试了下, 应该可行...注入store应该是最好的方法了...