關於引入nuxt到項目中的思考
爲何前端要引入同構SSR
a.爲了更好的seo和首屏加載速度css
b.引入BFF層,爲前端賦能,提高前端解決問題的能力前端
nuxt帶來的優勢
1.更爲清晰嚴格的結構:nuxt相似於egg等框架提供了一套結構和約束機制,因此,基於nuxt基礎上創立項目,結構會更清晰一些。vue
2.簡單易上手,開箱即用,集成了ui框架,測試框架等。npx create-nuxt-app appName
一套下來就能夠直接運行起來,遷移成本較低node
關於同構SSR
- 雖然使用了服務端渲染,可是這個只能叫同構SSR,和傳統的服務端渲染仍是有區別的。目前同構SSR的本質就是集成頁面組件,路由,前端狀態,在服務端中運行生成快照,將生成的快照HTML傳給客戶端。須要注意的是,因爲同構的這種快照所需的計算量遠大於傳統服務端渲染,因此單機性能上,可能要弱於傳統服務端渲染。
- 同構SSR的實現得意於虛擬DOM的出現,虛擬DOM的最大好處並不是Diff算法而是爲前端賦能,把HTML的DOM抽象化,能夠在服務端、IOS、安卓甚至智能家電上運行。
- 同構SSR的實質是當用戶首次請求時,經過node端生成一個HTML快照給前端,以後用戶在當前頁面上的操做,其實都是一個SPA的操做交互,前端的路由交互仍是依靠history路由去處理,而非傳統路由,因此其實仍是一個「
SPA
」。這樣的處理,能夠在保證首屏速度時,同時,減小服務器壓力,提高用戶體驗,彌補同構渲染性能問題。
Nuxt入門
構建
npm
npx create-nuxt-app <項目名>
複製代碼
yarn
yarn create nuxt-app <項目名>
複製代碼
目錄結構
簡寫
srcios
~ or @
複製代碼
root folderweb
~~ or @@
複製代碼
默認root和src是一致的ajax
Nuxt.config.js
踩坑:算法
1.Nuxt.config.js
文件未使用babel處理
vuex
nuxt.config.js
是nuxt提供的核心配置文件express
開發時,nuxt.config.js
中的修改不會直接熱更新,須要手動在命令行中輸入rs
從新執行一次
asyncData
asyncData方法會在組件(限於頁面組件)每次加載以前被調用。它能夠在服務端或路由更新以前被調用。 在這個方法被調用的時候,第一個參數被設定爲當前頁面的上下文對象,你能夠利用 asyncData方法來獲取數據,Nuxt.js 會將 asyncData 返回的數據融合組件 data 方法返回的數據一併返回給當前組件。
關於asyncData的理解
想象一下同構渲染的場景,當首次訪問的時候,服務端返回一個HTML的快照;後續用戶在改頁面上操做,則是用戶直接從瀏覽器發出請求到服務端。那麼咱們須要對數據的操做,爲了不寫兩套代碼,運行在node端和瀏覽器端,咱們須要這樣一個函數,可以判斷瀏覽器端仍是服務端,自動化的處理數據請求。
因此,nuxt提供了asyncData
這樣一個方法,用來處理同時會在服務端以及瀏覽器端進行的數據請求,asyncData
方法第一個參數被定義爲nuxtjs的上下文對象,經過nuxtjs的上下文對象,能夠獲取到路由參數,使用自定義的nuxtjs插件,對錯誤參數進行處理等等。
即:同構邏輯執行的接口函數
上下文對象中的參數
app
params
res,req
$axios等
獲取異步數據
默認axios,須要返回res中的data
如何使用asyncData
使用 async或await
export default { async asyncData ({ params }) { let { data } = await axios.get(`https://my-api/posts/${params.id}`) return { title: data.title } } } 複製代碼
使用 Promise
export default { asyncData ({ params }) { return axios.get(`https://my-api/posts/${params.id}`) .then((res) => { return { title: res.data.title } }) } } 複製代碼
不能使用this
SSR邏輯
首次請求頁面,會觸發SSR,當在當前頁面進行跳轉時,則會經過AJAX的方式去請求接口,CSR的方式去生成新的頁面。
預處理
nuxt繼承了vue cli3的預處理配置,若是想使用pug,scss,stylus等只須要在使用時執行npm intall 或者yarn add
npm install --save-dev pug@2.0.3 pug-plain-loader coffeescript coffee-loader node-sass sass-loader
複製代碼
跨域請求
npm i @nuxtjs/proxy -D
複製代碼
modules: [ '@nuxtjs/axios', '@nuxtjs/proxy' ], axios: { proxy: true }, proxy: { '/api': { target: 'http://example.com', pathRewrite: { '^/api' : '/' } } } 複製代碼
noSSR
能夠經過noSSR包裹,來實現CSR,場景,當頁面很長時,能夠經過底部使用CSR渲染來減小服務器負載。
支持文字形式以及插槽形式
<no-ssr placeholder="Loading..."> <!-- 此組件僅在客戶端呈現 --> <comments /> </no-ssr> 複製代碼
<no-ssr> <!-- 此組件僅在客戶端呈現 --> <comments /> <!-- loading indicator --> <comments-placeholder slot="placeholder" /> </no-ssr> 複製代碼
路由跳轉
NuxtLink
<NuxtLink :to="'/users/'+user.id"> {{ user.name }} </NuxtLink> 複製代碼
router.push
this.$router.push(`/detail/${topicItem.postid}`); 複製代碼
全局CSS
nuxt.config.js
css: [ 'element-ui/lib/theme-chalk/index.css', '~/assets/main.scss' ], 複製代碼
頁面跳轉間的loading
loading: '~/components/loading.vue', 複製代碼
layout
對應layout目錄下自定義的vue文件名
layout: 'dark', 複製代碼
動態佈局適應移動端
layout: (context) => context.isMobile ? 'mobile' : 'desktop' 複製代碼
中間件
nuxt.config.js
router: { middleware: ['visits', 'user-agent'] } 複製代碼
export default function (context) { const userAgent = process.server ? context.req.headers['user-agent'] : navigator.userAgent context.isMobile = /Android|webOS|iPhone|iPad|BlackBerry/i.test(userAgent) } 複製代碼
中間件基於路由,路由改變時將執行,執行流程順序:
- nuxt.config.js
- 匹配佈局
- 匹配頁面
nuxt中的中間件概念是基於路由層面的方法,能夠分別在nuxt.config.js
、layouts
、pages
中配置,分別對應所有頁面的中間件、全部使用同一佈局的中間件、單一頁面的中間件。若是同時配置一箇中間件在三個位置,則具體到單個頁面,會執行三次。
使用舉例 nuxt.config.js
中
//nuxt.config.js中router的配置 router: { middleware: 'auth', }, 複製代碼
layouts
和pages
中
middleware:'auth' 複製代碼
注意
場景:在頁面中清空cookie(這時vuex狀態並不會清空),而後點擊連接進行spa操做,執行middleware時,vuex中狀態仍是原來狀態
插件機制
三方庫,例如axios
import axios from "axios"; ... async asyncData() { let { data } = await axios.get( `https://api.isoyu.com/api/News/new_list?type=1&page=20/new_list?type=1&page=20}` ); return { topicList: data.data }; } 複製代碼
nuxt.config.js
plugins: [ { src: '@/plugins/element-ui' }, { src: '@/plugins/vue-notifications.js', mode: 'client' } ], 複製代碼
mode 能夠選擇client以及server。
注入vue實例
plugins/vue-inject.js
import Vue from 'vue' Vue.prototype.$myInjectedFunction = (string) => console.log("This is an example", string) 複製代碼
nuxt.config.js
export default { plugins: ['~/plugins/vue-inject.js'] } 複製代碼
注入 context
plugins/ctx-inject.js
export default ({ app }, inject) => { // Set the function directly on the context.app object app.myInjectedFunction = (string) => console.log('Okay, another function', string) } 複製代碼
nuxt.config.js
export default { plugins: ['~/plugins/ctx-inject.js'] } 複製代碼
使用
export default { asyncData(context){ context.app.myInjectedFunction('ctx!') } } 複製代碼
同時注入
plugins/combined-inject.js
export default ({ app }, inject) => { inject('myInjectedFunction', (string) => console.log('That was easy!', string)) } 複製代碼
nuxt.config.js
export default { plugins: ['~/plugins/combined-inject.js'] } 複製代碼
使用
export default { mounted(){ this.$myInjectedFunction('works in mounted') }, asyncData(context){ context.app.$myInjectedFunction('works with context') } } 複製代碼
store/index.js
export const state = () => ({ someValue: '' }) export const mutations = { changeSomeValue(state, newValue) { this.$myInjectedFunction('accessible in mutations') state.someValue = newValue } } export const actions = { setSomeValueToWhatever ({ commit }) { this.$myInjectedFunction('accessible in actions') const newValue = "whatever" commit('changeSomeValue', newValue) } } 複製代碼
注意:1.插件應該是按照nuxt.config.js
中的順序,依次執行的
錯誤提示
async asyncData({ $axios, params, error, app }) { error({ statusCode: 404, message: "Topic not found" }); } 複製代碼
nuxt深刻理解
生命週期
首先咱們須要瞭解一下這個框架的生命週期,在開發過程當中,可能會碰到一些問題難以定位,需求沒法實現,也許,經過nuxt的生命週期就能幫助你比較好的定位問題,解決問題。
首先,請求發生時,首選會執行nuxtServerInit
,處理vuex中的狀態,也就是先處理整個APP的狀態,而後纔會處理單個具體路由下的週期。首先會處理middleware
,前後會處理nuxt.config.js
中的配置,匹配的layout
(nuxt提供的模版佈局),匹配的頁面。在這個環節以後,會處理單個頁面中設置的validate函數(用來校驗路由參數)。最後,會經過nuxt提供的asyncData
以及fetch
的函數,去獲取單個page
的數據。須要注意的是,在這一些列生命週期後,會進入vue的渲染過程。
一次請求過程當中nuxt的生命週期順序
服務端
1.首先執行插件(全部在nuxt.config.js
中寫入的能夠在服務端運行插件,不論是否在當前頁面)
2.執行nuxtServerInit
3.執行middleware
:
a. middleware
會前後執行nuxt.config.js
中配置的middleware
;
b. layouts
中配置的middleware
;
c. pages
中的middleware
。
4.執行validate方法,校驗頁面參數是否正確
5.執行頁面中的asyncData以及fetch方法
6.真正進入vue的生命週期中,按前後順序,beforecreated
,created
瀏覽器端
7.執行插件(全部在nuxt.config.js
中寫入能夠在瀏覽器端執行的插件)
8.進入vue的生命週期中,再在瀏覽器端運行beforecreated
和created
一遍。
附:
1.plugin和nuxtServerInit僅在首次刷新頁面時會執行,後續點擊頁面內跳轉不會再執行plugin和nuxtServerInit中的方法。若是打開新頁面會再次觸發plugin和nuxtServerInit方法。
理解:
1.插件會在全部模塊運行以前運行,並且每次請求都會運行,因此若是項目較大,訪問量大的狀況下,僅將必要的方法寫到插件中,優化性能。
2.同構渲染框架提供的額外生命週期以及方法都是在真正的vue生命週期以前的,緣由是vue的生命週期是不能夠異步的,因此,爲了知足開發的需求,全部生命週期和方法都應該是在vue生命週期以前去執行的,理解好這一點,會方便入手nuxt開發。
3.beforeCreated
和created
生命週期實際上是同時在服務端和瀏覽器端執行的,「同構」渲染的來源之一。
4.插件,nuxtServerInit
,middleware
,validate
,asyncData
,根據執行順序和具體需求,選擇好適合的生命週期和方法去處理開發需求是頗有必要的
解決跨域開發的問題(cookie穿透)
問題場景描述:
本地開發若是是localhost
,服務端API域名爲test.com
。那麼當首次請求時,就會涉及到一個cookie(token)「預取」的過程,雖然引入了node端進行了同構渲染,可是cookie和token做爲用戶身份的憑證仍是保存在瀏覽器上,因此須要一個cookie從瀏覽器到node端,再由node端到服務端API的這樣一個過程。
解決方案:
因此,當首次請求頁面時,頁面的權限校驗以及相關數據,應該是從node端向服務端請求的,咱們須要瀏覽器端的cookie(token)。針對cookie預取(穿透)在早期有不少種不一樣的方案,有存到狀態管理器中的,有修改asyncData方法中的,個人建議是將數據請求單獨封裝成插件,利用asyncData同時在瀏覽器和node端工做的能力,在asyncData中,經過封裝後的axios插件去處理對應的問題。
同時,當咱們首次請求時,面臨一個開發問題,若是是localhost首次訪問,那麼瀏覽器首次請求時,是不會攜帶着test.com
的cookie的。本地開發就存在問題,因此這裏想出了有兩種方案,1.直接修改本地的ip指向爲test.com
,mac用戶推薦helm,簡單好用,切換環境。2.能夠在首次請求時,先訪問一個空白頁面,而後經過ajax跨域請求使nuxt獲得cookie,而後再從空白頁面跳轉回訪問的頁面,從而實現獲得想要的cookie。
下面是封裝一個axios插件的代碼
export default function ({ $axios, redirect, req, route, error }, inject) { // Create a custom axios instance let cookie = '' if (process.server) { if (req.headers.cookie) { cookie = req.headers.cookie } } else { cookie = document.cookie } const instance = $axios.create({ headers: { common: { Accept: 'text/plain, */*' }, }, withCredentials: true, // default }) //... instance.setBaseURL(process.env.API_HOST) process.server ? instance.setHeader(cookie) : '' 複製代碼
須要知道,瀏覽器端是不能夠設置header中的cookie參數,而服務端是能夠的,因此咱們在服務端手動設置一下cookie便可,經過process.server
去判斷是否爲服務端。這裏由於項目中是cookie這樣處理,若是token的話實際上是同樣的,只須要多一個把token從cookie中取出放到token裏的過程。
這樣,就實現了cookie(token)預取的過程,簡單方便有效。同時咱們也能夠將一些錯誤處理以及請求自定義在這裏處理 。例如http錯誤狀態的統一處理
instance.onResponse(response => { if (response.status === 404) { //404處理 } return response.data }) 複製代碼
這裏須要注意一個問題,在插件中,是能夠直接使用error方法去處理跳轉錯誤頁面的,但若是asyncData
中有屢次數據請求,而且成功失敗不一時,會致使error執行錯誤,這裏能夠在插件中使用redirect
方法去執行,或者在asyncData
中的Promise.all()
完成後去處理錯誤狀態。
nuxt鑑權
1.若是頁面較少,且每一個頁面都有接口請求,能夠直接省略引入vuex的機制,直接在封裝的http請求插件中根據和後端約定的狀態碼,當狀態碼錯誤時,判斷未登錄,處理相關邏輯便可。
2.若是頁面較多,且用戶權限不一致,有部分頁面無接口請求,好比,靜態頁面上面有一個header攜帶用戶名稱這樣的頁面。在這種狀況下,須要使用vuex而且作vuex持久化,從而方便開發。官網中的例子使用了express-session
機制,感受沒有必要。使用session存儲數據也能夠,但感受數據量不大的狀況下,放在cookie中比較簡潔。
nuxt中vuex的使用
1.nuxtServerInit
能夠在nuxtServerInit中作一些請求
例子
store/userinfo
export const state = () => ({ userName: '', roleName: '', roleType: '', }) export const mutations = { UPDATE_USERINFO(state, { userName, roleName, roleType, netEaseUserEmail }) { state.userName = userName state.roleName = roleName state.roleType = roleType } } 複製代碼
store/index
export const actions = { async nuxtServerInit(store, { res, req, app }) { //處理userinfo信息初始化 if (!(store.state.userinfo && store.state.userinfo.netEaseUserEmail)) { const userinfoRes = await app.$http.get(path.getUserInfo); store.commit('userinfo/UPDATE_USERINFO', userinfoRes.data) } } } 複製代碼
引用位置
import { mapState } from 'vuex' export default { computed: { ...mapState('userinfo', ['userName', 'roleName']) } } 複製代碼
2.經過vuex-persistedstate
,js-cookie
,cookie-parser
,cookie
實現經過cookie的方式持久化Vuex
示例
import createPersistedState from 'vuex-persistedstate' import * as Cookies from 'js-cookie' export default ({ store, req, res, app }) => { createPersistedState({ key: 'vuexnuxt', storage: { getItem: key => process.client ? Cookies.getJSON(key) : cookie.parse(req.headers.cookie || '')[key], setItem: (key, value) => process.client ? Cookies.set(key, value, { expires: 365 }) : res.cookie(key, value, { expires: new Date(Date.now() + 60 * 60 * 1000 * 24 * 365) }), removeItem: key => process.client ? Cookies.remove(key) : res.clearCookie(key) } })(store) } 複製代碼
經過這種辦法,能夠實現vuex的持久化支持,不論是在node端仍是瀏覽器端,均可以訪問到經過cookie
持久化的vuex
附:對cookie的操做瀏覽器端使用js-cookie
服務端使用cookie-parser
處理。嘗試了cookie-universal-nuxt
這個插件,放棄。(使用後頁面白屏卡死,可能該插件這個場景下出現了死循環)
對於vuex的持久化有多種選擇,locaostorage,sessionstorage,session等,但從使用方便行角度來講,cookie是比較好的選擇。