vue服務端渲染之nuxtjs


前言

本篇主要針對nuxtjs中的一些重要概念整理和代碼實現!

在學習vue服務端渲染之前,先搞清楚幾個概念:

  • 什么是客戶端渲染(CSR)
  • 什么是服務端渲染(SSR)
  • CSR和SSR有什么異同

客戶端渲染(CSR):當用戶在瀏覽器中輸入網址,打開網頁,此時的頁面只有樣式和一些html代碼構成的空殼頁面,並沒有數據。這就需要我們通過執行js代碼,請求相關數據,請求到數據之后,通過模板(vue),將這些數據渲染到頁面,最終呈現給用戶完整的頁面。

服務端渲染(SSR):當用戶在瀏覽器中輸入網址,打開網頁,此時的后端會根據請求的網址,拿到相關頁面並將數據填入到頁面,完成頁面的渲染,最后將完整的頁面返回給客戶端(瀏覽器),呈現到用戶面前。

利弊:客戶端渲染,就是現在非常流行的前后分離的開發模式,極大程度的減輕后端壓力,但是對SEO不友好;服務端渲染對SEO友好,但是對后端以及服務器的性能要求較高。

 

正文:

先用node實現簡易的服務端渲染:

const Vue = require('vue');
const server = require('express')();

server.get('/',(req,res)=>{
  
  //創建 Vue 實例
  const app = new Vue({
    template:`<div>hello vue SSR!</div>`
  })

  // 創建 renderer 沒下載的需要手動下載 npm i vue-server-renderer --save改包用於vue的服務端渲染
  const renderer = require('vue-server-renderer').createRenderer(); 

  renderer.renderToString(app).then(html=>{
    res.send(`
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
      </head>
      <body>
        ${html}
      </body>
      </html>
    `)
  }).catch(err=>{
    console.log(err)
  })
})

server.listen(8001)

檢查源代碼:

 

下面使用nuxjs來構建項目!

首先,使用nuxt提供的腳手架( create-nuxt-app)構建項目,根據提示一步一步操作。

 

 主要注意倆點:1.Choose custom server framework,選擇服務端渲染的框架,我們這里選擇的是express

        2.Choose rendering mode 這個選項可以選擇SSR和Single Page APP,因為我們需要構建服務端渲染多頁面項目,所以這里選擇SSR

下面就是項目創建完成后的目錄結構:

 

目錄結構:

  • assets::資源目錄,用於組織未編譯的靜態資源(css文件、圖標等)
  • components: 組件目錄,用於組織應用的vue組件,這些組件不會像頁面組件(pages中的組件)那樣有 asyncData 方法的特性
  • layouts:布局目錄,用於組織應用的布局組件,若無額外配置,該目錄不能被重命名
  • middleware:中間件目錄,用於存放應用的中間件
  • pages: 頁面目錄,用於組織應用的路由及視圖,Nuxt.js 框架讀取該目錄下所有的 .vue文件並自動生成對應的路由配置,若無額外配置,該目錄不能被重命名
  • plugins:插件目錄,用於組織那些需要在 根vue.js應用實例化之前需要運行的JavaScript插件
  • server:服務器目錄,用於服務端渲染
  • static:靜態文件目錄,用於存放應用的靜態文件,此類文件不會被 Nuxt.js 調用 Webpack 進行構建編譯處理。服務器啟動的時候,該目錄下的文件會映射至應用的根路徑 / 下(/static/robots.txt 映射至 /robots.txt),若無額外配置,該目錄不能被重命名
  • store:store目錄,用於組織應用的 Vuex狀態樹 文件,若無額外配置,該目錄不能被重命名
  • nuxt.config.js:用於組織 Nuxt.js 應用的個性化配置,以便覆蓋默認配置,若無額外配置,該目錄不能被重命名
  • package.json:用於描述應用的依賴關系和對外暴露的腳本接口,該文件不能被重命名

 項目跑起來:

 檢查源碼,頁面的所有數據都能夠看到。

 

接下來正式學習nuxt,主要分為3部分:

  • 生命周期
  • 路由
  • vuex狀態樹

一、生命周期(鈎子函數)

  • nuxtServerInit => 服務器初始化
  • middleware => 中間件
  • validate() => 參數校驗
  • asyncData()和fetch() => 異步數據處理
  • render => 客戶端渲染
  • beforeCreate和created => 這倆個鈎子服務端和客戶端都存在,可以拿到服務端的上下文以及組件本身

下面具體使用這些生命周期:

1.nuxtServerInit 

在store目錄中新建index.js:

export const actions = {
  nuxtServerInit(store, context){
    console.log('我是nuxtServerInit生命周期!', store, context)
  }
}

項目跑起來后,在終端會打印相關信息:

 

 2.middleware 

middleware中間件可以在配置文件中、布局頁面、頁面組件中使用。

在nuxt.config.js配置文件中,將middleware寫進router配置中,可以進行全局的導航守衛:

module.exports = {
  mode: 'universal',
  router: {
    middleware: 'isLogin'
  },
  ...

接下來在middleware目錄中新建isLogin.js文件:

export default (context) => {
  console.log('我是全局守衛')
}

打印:

 

 在布局頁面中使用中間件,在layout目錄下的default.vue中:

<script>
  export default {
    // middleware: 'isLogin',
    middleware(){
      console.log('我是布局頁面的中間件!')
    },
  }
</script>

在頁面組件內中間件可以直接寫外部之前定義的中間件,也可以用中間件鈎子!

 

 同樣,在頁面組件中,pages目錄下的index.vue中:

<script>
  export default {
    // middleware: 'isLogin',
    middleware(){
      console.log('我是頁面組件的中間件!')
    },
  }
</script>

 

 通過打印的順序可以看出,中間件的執行順序是:nuxt.config.js文件 => 布局文件 => 頁面文件,所有中間件函數的參數中,都能拿到服務端的上下文信息context。

3.validate()

validate()需要定義在頁面組件內,即pages目錄下的頁面,可以用來進行參數校驗,攔截是否可以進入該頁面。具體實現,在pages/index.vue中:

//js 代碼
export default {
  // middleware: 'isLogin',
  middleware(){
    console.log('我是頁面組件的中間件!')
  },
  validate(context){
    const {params, query} = context;
    //對傳過來的params和query進行校驗
    //...
    console.log('我是頁面validate參數校驗')
    return true; // 校驗通過,校驗失敗返回false,進入到404頁面
  },
  components: {
    Logo
  }
}

 

 4.asyncData()和fetch()

這倆個鈎子也是在頁面組件中使用,pages/index.vue中:

  asyncData(context){
    // 異步讀取數據,處理相關業務邏輯
    console.log('我是asyncData')
    //最后返回數據
    return {
      a: 1
    };
  },

  fetch(context){
    // 異步讀取數據,處理相關業務邏輯,將數據提交給vuex
    console.log('我是fetch')
  },

 

5.beforeCreate()和created()

 在pages/index.vue中:

...
fetch(context){
    // 異步讀取數據,處理相關業務邏輯,將數據提交給vuex
    console.log('我是fetch')
  },
  beforeCreate() {
    console.log('我是beforeCreate')
  },
  created() {
    console.log('我是create')
  },
...

客戶端:

 服務端:

 

二、路由(約定式、自定義)

1.約定式路由:依據pages目錄結構自動生成 vue-router模塊的路由配置。

1>基礎路由:

 在layouts目錄中的default.vue:

<template>
  <div>
    <!--  聲明式跳轉:nuxt-link跟vue中的router-link一樣,負責路由跳轉  -->
    <nuxt-link to="/">首頁</nuxt-link>
    <nuxt-link to="/tabA">tabA</nuxt-link>
    <nuxt-link to="/tabB">tabB</nuxt-link>

    <!--  展示區,跟vue中的router-view一樣  -->
    <nuxt/>
  </div>
</template>

效果:

nuxt自動生成的路由為:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'tabA',
      path: '/tabA',
      component: 'pages/tabA.vue'
    },
    {
      name: 'tabB',
      path: '/tabB',
      component: 'pages/tabB.vue'
    }
  ]
}

2>動態路由

 在 Nuxt.js 里面定義帶參數的動態路由,需要創建對應的以下划線“_”作為前綴的 Vue 文件 或 目錄。

pages/tabA.vue:

<template>
  <div>
    <h3>
      tabA
    </h3>
    <!--  倆種路由傳參方式  -->
    <nuxt-link to="/tabA/1?a=tabA1">tabA動態路由1</nuxt-link>
    <!--  name: 'tabA-id',這里-id必須是tabA目錄下的_id.vue的名稱,且將 _ 改為 -   -->
    <nuxt-link :to="{name: 'tabA-id', params:{id: 2}, query:{a: 'tabA2'}}">tabA動態路由2</nuxt-link>

    <!--  子路由的展示區  -->
    <nuxt/>
  </div>
</template>

pages/tabA/_id.vue:

<template>
    <div>
      <h3>我是tabA的動態路由頁</h3>
    </div>
</template>

效果:

這里會發現一個問題,路由為/tabA的時候,也打開了動態路由頁,這是因為當路由為tabA時,會發現它下面還有子路由,由於沒有指定默認的頁面,因此會找到一個子路由進行渲染。為了解決這個問題,可以在tabA目錄下新建一個index.vue。

tabA/index.vue:

<template>
    <h3>我是動態子路由默認頁</h3>
</template>

此時nuxt自動生成的路由信息為:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'tabA-id',
      path: '/tabA/:id',
      component: 'pages/tabA/_id.vue'
    },
    ...  
  ]
}

 3>嵌套路由

 創建內嵌子路由,你需要添加一個 Vue 文件,同時添加一個與該文件同名的目錄用來存放子視圖組件。

 在tabA目錄下新建tabC目錄和tabC.vue

 tabA/tabC.vue:

<template>
  <div>
    <!--  子路由展示區  -->
    <nuxt></nuxt>
  </div>
</template>

tabA/tabC/index.vue:

<template>
    <div>
      <h3>我是tabC</h3>
    </div>
</template>

從上圖可以看到倆種顏色的區域,是倆級展示區,通常我們一個頁面只需要一個展示區,因此需要改一下項目的目錄結構:

 在pages目錄下的頁面為一級展示區,其中有tabA、tabB,接下來將tabA目錄下的頁面也設置成一級展示區。將tabA.vue內容拷貝至tabA目錄下的index.vue中,並刪除tabA.vue。

2、擴展路由

nuxt中支持聲明式路由,同樣也支持自定義路由,根據路由渲染指定的視圖,需要在nuxt.config.js中配置:

router: {
    extendRoutes(routes, resolve){
      routes.push({
        name: 'tabD',
        path: '/tabD',
        component: resolve(__dirname, 'pages/extendTabD.vue')
      })
    }
  },

在pages中新建extendTabD.vue:

pages/extendTabD.vue

<template>
    <div>
      <h3>我是擴展路由tabD</h3>
    </div>
</template>

在layouts/default.vue中增加鏈接: <nuxt-link to="/tabD">tabD</nuxt-link> 

 

3、路由過渡動畫

1>全局過渡動畫:

transition.css:

/*  路由統一動效 */

/* 動畫形式 */
.page-enter-active, .page-leave-active{
  transition:  opacity .5s;
}

/* 入 退 */
.page-enter, .page-leave-active{
  opacity:  0;
}

nuxt.config.js:

/*
  ** Global CSS
  */
  css: [
    'assets/css/transition.css'
  ],

2>局部過渡動畫

extendTabD.vue:

<template>
    <div>
      <h3>我是擴展路由tabD</h3>
    </div>
</template>

<script>
  export default {
    name: "extendTabD",
    transition: 'tabd'
  }
</script>

<style scoped>
  .tabd-enter-active, .tabd-leave-active{
    transition: .5s ease all;
  }
  .tabd-enter, .tabd-leave-active{
    margin-left: -1000px;
  }
</style>

 

 4、路由守衛

  • 前置
  • 后置
  • 組件內部

1>前置:

全局守衛:

  • 在nuxt.config.js的router內引入middleware
  • 在layouts文件中定義middleware
  • 在插件中定義前置全局守衛,beforeEach()

組件獨享守衛:

  • 在頁面中引入middleware或middleware()
  • 跟vue-router一樣的組件內部鈎子函數,比如:beforeRouteEnter

2>后置:

在插件中定義后置全局守衛,afterEach()

 

 具體實現:

全局守衛:

 a.借助middleware實現前置的路由守衛,在生命周期的middleware中已經基本實現。

middleware/isLogin.js:

export default (context) => {
  console.log('我是全局守衛')
  const {redirect} = context
  redirect('/tabD') // 利用redirect()頁面重定向
}

b.借助插件實現前置、后置全局守衛:

plugins/router.js

export default ({app, redirect})=>{
  //app == vue實例
  //全局前置守衛,進入的頁面不是tabA的頁面,都會重定向到tabA
  app.router.beforeEach((to, from, next)=>{
    if(to.name == 'tabA'){
      next()
    }else{
      redirect({name: 'tabA'})
    }
  });
  //全局后置守衛
  app.router.afterEach((to, from)=>{
    console.log('我是后置守衛')
  })
}

nuxt.config.js中配置plugins:

plugins: [
    '~/plugins/router.js'
  ],

組件內守衛:

extendTabD.vue:

<template>
    <div>
      <h3>我是擴展路由tabD</h3>
    </div>
</template>

<script>
  export default {
    name: "extendTabD",
    transition: 'tabd',
    beforeRouteEnter(to, from, next){
      alert('haha');
    }
  }
</script>

 

三、vuex狀態樹

 nuxt中集成了vuex,因此我們在項目中不需要下載引入,而是按照約定使用:

 nuxt會嘗試找到 src 目錄(默認是應用根目錄)下的 store 目錄,如果該目錄存在,它將做以下的事情:

  1. 引入vuex模塊
  2. 將vuex模塊加到vendors構建配置中
  3. 設置vue根實例的store配置項

nuxt中的vuex使用方式跟vue中大同小異,除了不需要手動引入之外,在模塊化中,store目錄下的每個子模塊(除了根模塊index.js),都會被轉換成為狀態樹指定命名的子模塊,其它使用方式跟vue項目中的vuex基本相同。

store/index.js:

const state = ()=>({

})
const mutations = {
  
}

const actions = {
  
}
export default {
  state,
  mutations,
  actions
}

store/user.js:

const state = () => ({
  isLogin: false
})

const mutations = {
  change_Login(state, isLogin) {
    state.isLogin = isLogin
  }
}

const actions = {
  changeLogin({commit},isLogin) {
    commit('change_Login', isLogin)
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

pages/tabB.vue:

<template>
  <div>
    <h3>tabB</h3>
    <h4>登錄狀態:{{isLogin ? '已登錄' : '未登錄'}}</h4>
  </div>
</template>

<script>
  import { mapState } from 'vuex';
  export default {
    name: "TabB",
      computed:{
        ...mapState({
            isLogin: state => state.user.isLogin
        })
      },
      created() {
        setTimeout(()=>{
          this.$store.dispatch('user/changeLogin', true)
        }, 1000)
    }
  }
</script>

效果:一秒后,登錄狀態從“未登錄”變為“已登錄”。

 

以上就是我對nuxt學習過程中一些重要概念的理解和實踐,有什么不足歡迎評論指正!

 

腳踏實地行,海闊天空飛


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM