nuxt入門踩坑記錄

關於引入nuxt到項目中的思考

爲何前端要引入同構SSR

a.爲了更好的seo和首屏加載速度css

b.引入BFF層,爲前端賦能,提高前端解決問題的能力前端

nuxt帶來的優勢

1.更爲清晰嚴格的結構:nuxt相似於egg等框架提供了一套結構和約束機制,因此,基於nuxt基礎上創立項目,結構會更清晰一些。vue

2.簡單易上手,開箱即用,集成了ui框架,測試框架等。npx create-nuxt-app appName一套下來就能夠直接運行起來,遷移成本較低node

關於同構SSR

  1. 雖然使用了服務端渲染,可是這個只能叫同構SSR,和傳統的服務端渲染仍是有區別的。目前同構SSR的本質就是集成頁面組件,路由,前端狀態,在服務端中運行生成快照,將生成的快照HTML傳給客戶端。須要注意的是,因爲同構的這種快照所需的計算量遠大於傳統服務端渲染,因此單機性能上,可能要弱於傳統服務端渲染。
  2. 同構SSR的實現得意於虛擬DOM的出現,虛擬DOM的最大好處並不是Diff算法而是爲前端賦能,把HTML的DOM抽象化,能夠在服務端、IOS、安卓甚至智能家電上運行。
  3. 同構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)
}
複製代碼

中間件基於路由,路由改變時將執行,執行流程順序:

  1. nuxt.config.js
  2. 匹配佈局
  3. 匹配頁面

nuxt中的中間件概念是基於路由層面的方法,能夠分別在nuxt.config.jslayoutspages中配置,分別對應所有頁面的中間件、全部使用同一佈局的中間件、單一頁面的中間件。若是同時配置一箇中間件在三個位置,則具體到單個頁面,會執行三次。

使用舉例 nuxt.config.js

//nuxt.config.js中router的配置
  router: {
    middleware: 'auth',
  },
複製代碼

layoutspages

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深刻理解

生命週期

image

首先咱們須要瞭解一下這個框架的生命週期,在開發過程當中,可能會碰到一些問題難以定位,需求沒法實現,也許,經過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的生命週期中,再在瀏覽器端運行beforecreatedcreated一遍。

附:

1.plugin和nuxtServerInit僅在首次刷新頁面時會執行,後續點擊頁面內跳轉不會再執行plugin和nuxtServerInit中的方法。若是打開新頁面會再次觸發plugin和nuxtServerInit方法。

理解:

1.插件會在全部模塊運行以前運行,並且每次請求都會運行,因此若是項目較大,訪問量大的狀況下,僅將必要的方法寫到插件中,優化性能。

2.同構渲染框架提供的額外生命週期以及方法都是在真正的vue生命週期以前的,緣由是vue的生命週期是不能夠異步的,因此,爲了知足開發的需求,全部生命週期和方法都應該是在vue生命週期以前去執行的,理解好這一點,會方便入手nuxt開發。

3.beforeCreatedcreated生命週期實際上是同時在服務端和瀏覽器端執行的,「同構」渲染的來源之一。

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是比較好的選擇。

相關文章
相關標籤/搜索