一個網站一旦涉及到多用戶, 就很難從 Cookies 中逃脫, Vue SSR 的 cookies 也真算是遇到的一個不小的問題, 從開始玩 SSR 開始到如今, 一共想出了3種方案, 從最先的把 Cookies 注入到 state 中, 到把 Cookies 注入到 global, 到如今的將 Cookies 注入到組件的 asyncData 方法.javascript
隨着 Vue 的升級, 第一種方案已經再也不適用, 第二種也有很多的限制, 因而想到第三種方案, 下來就說說具體實現的方法:html
第一種方案已經再也不適用, 這裏再也不細說vue
思路: 將 cookies 注入到 ssr 的 context裏, 而後在請求 api 時讀取, 再追加到 axios 的header 裏java
1, 首先在 server.js 裏將 cookies 加到 context裏ios
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__
git
2, 在 api.js 裏讀取 cookiesgithub
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。應用程序代碼與服務器進程隔離, 因此每一個訪問的用戶上下文都是獨立的, 不會互相影響.vuex
可是從Vue@2.3.0
開始, 在createBundleRenderer
方法的選項中, 添加了runInNewContext
選項, 使用 runInNewContext: false
,bundle 代碼將與服務器進程在同一個 global 上下文中運行,因此咱們不能再將 cookies 放在 global, 由於這會讓全部用戶共用同一個 cookies.axios
爲何如今不這麼作?
那咱們繼續將runInNewContext
設置成true
, 不就行了嗎? 固然也是能夠的, 可是從新建立上下文並執行整個 bundle 仍是至關昂貴的,特別是當應用很大的時候.api
以我本身的博客爲例, 以前只有渲染 5 個路由組件, loadtest 的 rps, 有 50 左右, 可是後來把後臺的 12 個路由組件也加到 SSR 後, rps 直接降到了個位數...
因此出現瞭如今的第三種方案
思路: 將 Cookies 做爲參數注入到組件的asyncData
方法, 而後用傳參數的方法把 cookies 傳給 api, 不得不說這種方法很麻煩, 可是這是我的能想到的比較好的方法
仍是在 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) })
在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)
在組件裏, 把 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) } // ..... }
在 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
在 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 } }) }, }
若是你沒有將 axios 從新封裝, 那麼也能夠把第五步省略, 直接在第四部把 cookies 給 axios 便可
方案 2 具體實例: https://github.com/lincenying...
方案 3 具體實例: https://github.com/lincenying...
綜上, 若是你項目不大, 仍是直接用方案 2 吧, 項目有不少頁面, 而且大部分頁面是每一個用戶都同樣的, 能夠考慮方案 3, 或者你有什麼更好的方法, 歡迎討論
Vue SSR 對須要 SEO, 而且每一個用戶看到的內容都是一致的, 配合緩存, 將是一個很是好的體驗...