浅谈Vue SSR 的 Cookies 问题


Posted in Javascript onNovember 20, 2017

一个网站一旦涉及到多用户, 就很难从 Cookies 中逃脱, Vue SSR 的 cookies 也真算是遇到的一个不小的问题, 从开始玩 SSR 开始到现在, 一共想出了3种方案, 从最早的把 Cookies 注入到 state 中, 到把 Cookies 注入到 global, 到现在的将 Cookies 注入到组件的 asyncData 方法.

随着 Vue 的升级, 第一种方案已经不再适用, 第二种也有不少的限制, 于是想到第三种方案, 下来就说说具体实现的方法:

第一种方案

第一种方案已经不再适用, 这里不再细说

第二种方案

思路: 将 cookies 注入到 ssr 的 context里, 然后在请求 api 时读取, 再追加到 axios 的header 里

1, 首先在 server.js 里将 cookies 加到 context里

const context = {
 title: 'M.M.F 小屋',
 description: 'M.M.F 小屋',
 url: req.url,
 cookies: req.cookies
}
renderer.renderToString(context, (err, html) => {
 if (err) {
  return errorHandler(err)
 }
 res.end(html)
})

之后, Vue 会把 context 加到 global.__VUE_SSR_CONTEXT__

2, 在 api.js 里读取 cookies

import axios from 'axios'
import qs from 'qs'
import md5 from 'md5'
import config from './config-server'

const SSR = global.__VUE_SSR_CONTEXT__
const cookies = SSR.cookies || {}
const parseCookie = cookies => {
 let cookie = ''
 Object.keys(cookies).forEach(item => {
  cookie+= item + '=' + cookies[item] + '; '
 })
 return cookie
}

export default {
 async post(url, data) {
  const cookie = parseCookie(cookies)
  const res = await axios({
   method: 'post',
   url: config.api + url,
   data: qs.stringify(data),
   timeout: config.timeout,
   headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    cookie
   }
  })
  return res
 },
}

为什么可以这么做?

默认情况下,Vue 对于每次渲染,bundle renderer 将创建一个新的 V8 上下文并重新执行整个 bundle。应用程序代码与服务器进程隔离, 所以每个访问的用户上下文都是独立的, 不会互相影响.

但是从Vue@2.3.0开始, 在createBundleRenderer方法的选项中, 添加了runInNewContext选项, 使用 runInNewContext: false,bundle 代码将与服务器进程在同一个 global 上下文中运行,所以我们不能再将 cookies 放在 global, 因为这会让所有用户共用同一个 cookies.

为什么现在不这么做?

那我们继续将runInNewContext设置成true, 不就好了吗? 当然也是可以的, 但是重新创建上下文并执行整个 bundle 还是相当昂贵的,特别是当应用很大的时候.

以我自己的博客为例, 之前只有渲染 5 个路由组件, loadtest 的 rps, 有 50 左右, 但是后来把后台的 12 个路由组件也加到 SSR 后, rps 直接降到了个位数...

所以出现了现在的第三种方案

第三种方案

思路: 将 Cookies 作为参数注入到组件的asyncData方法, 然后用传参数的方法把 cookies 传给 api, 不得不说这种方法很麻烦, 但是这是个人能想到的比较好的方法

步骤1:

还是在 server.js 里, 把 cookies 注入到 context 中

const context = {
 title: 'M.M.F 小屋',
 url: req.url,
 cookies: req.cookies,
}
renderer.renderToString(context, (err, html) => {
 if (err) {
  return handleError(err)
 }
 res.end(html)
})

步骤2:

在entry-server.js里, 将cookies作为参数传给 asyncData 方法

Promise.all(matchedComponents.map(({asyncData}) => asyncData && asyncData({
 store,
 route: router.currentRoute,
 cookies: context.cookies,
 isServer: true,
 isClient: false
}))).then(() => {
 context.state = store.state
 context.isProd = process.env.NODE_ENV === 'production'
 resolve(app)
}).catch(reject)

步骤3:

在组件里, 把 cookies 做为参数给 Vuex 的 actions

export default {
 name: 'frontend-index',
 async asyncData({store, route, cookies}, config = { page: 1}) {
  config.cookies = cookies
  await store.dispatch('frontend/article/getArticleList', config)
 }
 // .....
}

步骤4:

在 Vuex 里将 cookies 做为参数给 api

import api from '~api'

const state = () => ({
 lists: {
  data: [],
  hasNext: 0,
  page: 1,
  path: ''
 },
})

const actions = {
 async ['getArticleList']({commit, state}, config) {
  // vuex 作为临时缓存
  if (state.lists.data.length > 0 && config.path === state.lists.path && config.page === 1) {
   return
  }
  let cookies
  if (config.cookies) {
   cookies = config.cookies
   delete config.cookies
  }
  const { data: { data, code} } = await api.get('frontend/article/list', {...config, cache: true}, cookies)
  if (data && code === 200) {
   commit('receiveArticleList', {
    ...config,
    ...data,
   })
  }
 },
}

const mutations = {
 ['receiveArticleList'](state, {list, hasNext, hasPrev, page, path}) {
  if (page === 1) {
   list = [].concat(list)
  } else {
   list = state.lists.data.concat(list)
  }
  state.lists = {
   data: list, hasNext, hasPrev, page, path
  }
 },
}

const getters = {

}

export default {
 namespaced: true,
 state,
 actions,
 mutations,
 getters
}

这里一定要注意, state 一定要用函数返回值来初始化 state, 不然会导致所有用户共用 state

步骤5:

在 api 里接收 cookies, 并加到 axios 的 headers 里

import axios from 'axios'
import qs from 'qs'
import config from './config-server'

const parseCookie = cookies => {
 let cookie = ''
 Object.keys(cookies).forEach(item => {
  cookie+= item + '=' + cookies[item] + '; '
 })
 return cookie
}

export default {
 get(url, data, cookies = {}) {
  const cookie = parseCookie(cookies)
  return axios({
   method: 'get',
   url: config.api + url,
   data: qs.stringify(data),
   timeout: config.timeout,
   headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    cookie
   }
  })
 },
}

第四种方案

步骤1:

还是在 server.js 里, 把 cookies 注入到 context 中

const context = {
  title: 'M.M.F 小屋',
  url: req.url,
  cookies: req.cookies,
}
renderer.renderToString(context, (err, html) => {
  if (err) {
    return handleError(err)
  }
  res.end(html)
})

步骤2:

在entry-server.js里, 将cookies作为参数传给 api.setCookies 方法, api.setCookies 是什么东西后面会有

api.setCookies(context.cookies) // 这一句
Promise.all(matchedComponents.map(({asyncData}) => asyncData && asyncData({
 store,
 route: router.currentRoute,
 cookies: context.cookies,
 isServer: true,
 isClient: false
}))).then(() => {
 // ...
}

步骤3:

重写 api.js

import axios from 'axios'
import qs from 'qs'
import config from './config-server'

const parseCookie = cookies => {
  let cookie = ''
  Object.keys(cookies).forEach(item => {
    cookie+= item + '=' + cookies[item] + '; '
  })
  return cookie
}

export default {
  api: null,
  cookies: {},
  setCookies(value) {
    value = value || {}
    this.cookies = value
    this.api = axios.create({
      baseURL: config.api,
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        cookie: parseCookie(value)
      },
      timeout: config.timeout,
    })
  },
  post(url, data) {
    if (!this.api) this.setCookies()
    return this.api({
      method: 'post',
      url,
      data: qs.stringify(data),
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
      }
    }).then(res => {
      return res
    })
  },
  get(url, params) {
    if (!this.api) this.setCookies()
    return this.api({
      method: 'get',
      url,
      params,
    }).then(res => {
      return res
    })
  }
}

步骤4:

没有步骤4了, 直接引入 api 调用即可

如果你没有将 axios 重新封装, 那么也可以把第五步省略, 直接在第四部把 cookies 给 axios 即可

方案 2 具体实例: https://github.com/lincenying/mmf-blog-vue2-ssr

方案 3 具体实例: https://github.com/lincenying/mmf-blog-vue2-pwa-ssr

方案 4 具体实例: https://github.com/lincenying/mmf-blog-vue2-pwa-ssr

综上, 如果你项目不大, 还是直接用方案 2 吧, 项目有很多页面, 并且大部分页面是每个用户都一样的, 可以考虑方案 3, 或者你有什么更好的方法, 欢迎讨论

Vue SSR 对需要 SEO, 并且每个用户看到的内容都是一致的, 配合缓存, 将是一个非常好的体验...

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Jquery easyUI 更新行示例
Mar 06 Javascript
跟我学Node.js(四)---Node.js的模块载入方式与机制
Jun 04 Javascript
node.js开发中使用Node Supervisor实现监测文件修改并自动重启应用
Nov 04 Javascript
javascript实现很浪漫的气泡冒出特效
Sep 05 Javascript
JS随机洗牌算法之数组随机排序
Mar 23 Javascript
angularjs+bootstrap菜单的使用示例代码
Mar 07 Javascript
VUE实现一个分页组件的示例
Sep 13 Javascript
vue中for循环更改数据的实例代码(数据变化但页面数据未变)
Sep 15 Javascript
javascript实现电脑和手机版样式切换
Nov 10 Javascript
详解Vue.js中.native修饰符
Apr 24 Javascript
浅谈vue异步数据影响页面渲染
Oct 29 Javascript
使用 JavaScript 制作页面效果
Apr 21 Javascript
three.js中文文档学习之创建场景
Nov 20 #Javascript
Vue 中批量下载文件并打包的示例代码
Nov 20 #Javascript
VueJs 搭建Axios接口请求工具
Nov 20 #Javascript
Vue2 SSR渲染根据不同页面修改 meta
Nov 20 #Javascript
详解vue+css3做交互特效的方法
Nov 20 #Javascript
解读ES6中class关键字
Nov 20 #Javascript
利用canvas中toDataURL()将图片转为dataURL(base64)的方法详解
Nov 20 #Javascript
You might like
PHP中实现图片的锐化
2006/10/09 PHP
php源代码安装常见错误与解决办法分享
2013/05/28 PHP
php读取csv文件后,uft8 bom导致在页面上显示出现问题的解决方法
2013/08/10 PHP
PHP whois查询类定义与用法示例
2019/04/03 PHP
phpStorm2020 注册码
2020/09/17 PHP
IE 缓存策略的BUG的解决方法
2007/07/21 Javascript
JavaScript判断访问的来源是手机还是电脑,用的哪种浏览器
2013/12/12 Javascript
jQuery 仿百度输入标签插件附效果图
2014/07/04 Javascript
js操作css属性实现div层展开关闭效果的方法
2015/05/11 Javascript
JavaScript Ajax实现异步通信
2016/12/14 Javascript
基于Javascript实现的不重复ID的生成器
2016/12/25 Javascript
JavaScript中this的用法及this在不同应用场景的作用解析
2017/04/13 Javascript
JS获取今天是本月第几周、本月共几周、本月有多少天、是今年的第几周、是今年的第几天的示例代码
2018/12/05 Javascript
Node.js Buffer模块功能及常用方法实例分析
2019/01/05 Javascript
VUE实现Studio管理后台之鼠标拖放改变窗口大小
2020/03/04 Javascript
vue项目中使用rem,在入口文件添加内容操作
2020/11/11 Javascript
Python面向对象特殊成员
2017/04/24 Python
Python、PyCharm安装及使用方法(Mac版)详解
2017/04/28 Python
浅谈python中的占位符
2017/11/09 Python
实例讲解Python中浮点型的基本内容
2019/02/11 Python
python图形工具turtle绘制国际象棋棋盘
2019/05/23 Python
Python文件操作及内置函数flush原理解析
2020/10/13 Python
Python用Jira库来操作Jira
2020/12/28 Python
CSS3 优势以及网页设计师如何使用CSS3技术
2009/07/29 HTML / CSS
HTML5 video 上传预览图片视频如何设置、预览视频某秒的海报帧
2018/08/28 HTML / CSS
经济管理专业毕业生自荐信范文
2014/01/02 职场文书
大一军训感言
2014/01/09 职场文书
《太阳》教学反思
2014/02/21 职场文书
爱我中华教学反思
2014/04/28 职场文书
学生无故旷课检讨书
2014/09/20 职场文书
2014年学生会部门工作总结
2014/11/07 职场文书
2016新年致辞
2015/08/01 职场文书
聘用合同范本
2015/09/21 职场文书
2016年感恩节寄语
2015/12/07 职场文书
关于考试抄袭的检讨书
2019/11/02 职场文书
利用Selenium添加cookie实现自动登录的示例代码(fofa)
2021/05/08 Python