浅谈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 相关文章推荐
extjs中grid中嵌入动态combobox的应用
Jan 01 Javascript
JavaScript监听文本框回车事件并过滤文本框空格的方法
Apr 16 Javascript
jQuery实现延迟跳转的方法
Jun 05 Javascript
简单介绍jsonp 使用小结
Jan 27 Javascript
浅谈javascript中new操作符的原理
Jun 07 Javascript
jquery设置表单元素为不可用的简单代码
Jul 04 Javascript
详解Bootstrap各式各样的按钮(推荐)
Dec 13 Javascript
jQuery实现遮罩层登录对话框
Dec 29 Javascript
Javascript blur与click冲突解决办法
Jan 09 Javascript
jQuery 判断元素整理汇总
Feb 28 Javascript
微信小程序点击列表跳转到对应详情页过程解析
Sep 26 Javascript
原生JS实现pc端轮播图效果
Dec 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中new static() 和 new self() 的区别介绍
2015/01/09 PHP
PHP中is_file()函数使用指南
2015/05/08 PHP
CI配置多数据库访问的方法
2016/03/28 PHP
PHP5.5.15+Apache2.4.10+MySQL5.6.20配置方法分享
2016/05/06 PHP
PHP+百度AI OCR文字识别实现了图片的文字识别功能
2019/05/08 PHP
php文件后缀不强制为.php的实操方法
2019/09/18 PHP
Javascript变量函数浅析
2011/09/02 Javascript
js 可拖动列表实现代码
2011/12/13 Javascript
javascript小数四舍五入多种方法实现
2012/12/23 Javascript
jquery 元素控制(追加元素/追加内容)介绍及应用
2013/04/21 Javascript
Javascript学习笔记之 对象篇(一) : 对象的使用和属性
2014/06/24 Javascript
javascript中声明函数的方法及调用函数的返回值
2014/07/22 Javascript
jQuery中appendTo()方法用法实例
2015/01/08 Javascript
浅谈javascript中的Function和Arguments
2016/08/30 Javascript
100多个基础常用JS函数和语法集合大全
2017/02/16 Javascript
javascript函数的节流[throttle]与防抖[debounce]
2017/11/15 Javascript
JS排序算法之冒泡排序,选择排序与插入排序实例分析
2017/12/13 Javascript
360doc网站不登录就无法复制内容的解决方法
2018/01/27 Javascript
微信小程序关键字变色实现代码实例
2019/12/13 Javascript
Node.js API详解之 readline模块用法详解
2020/05/22 Javascript
在Pycharm中对代码进行注释和缩进的方法详解
2019/01/20 Python
Python OpenCV利用笔记本摄像头实现人脸检测
2020/08/20 Python
解决 jupyter notebook 回车换两行问题
2020/04/15 Python
python属于解释语言吗
2020/06/11 Python
Opencv常见图像格式Data Type及代码实例
2020/11/02 Python
美国肌肉和力量商店:Muscle & Strength
2019/06/22 全球购物
加拿大品牌鞋包连锁店:Little Burgundy
2021/02/28 全球购物
初中生学习的自我评价
2013/11/14 职场文书
自荐信格式技巧有哪些呢
2013/11/19 职场文书
求职信范文英文版
2014/01/05 职场文书
校友会欢迎辞
2014/01/13 职场文书
大学应届生的自我评价
2014/03/06 职场文书
单位介绍信格式
2015/01/31 职场文书
2015年度村委会工作总结
2015/04/29 职场文书
初二英语教学反思
2016/02/15 职场文书
Python内置类型集合set和frozenset的使用详解
2022/04/26 Python