1. 程式人生 > >從壹開始前後端分離 [ vue + .netcore 補充教程 ] 二八║ Nuxt 基礎:面向原始碼研究Nuxt.js

從壹開始前後端分離 [ vue + .netcore 補充教程 ] 二八║ Nuxt 基礎:面向原始碼研究Nuxt.js

前言

哈嘍大家週五好,又是一個開開心心的週五了,接下來就是三天小團圓啦,這裡先祝大家節日快樂咯,希望都沒有加班哈哈,今天公司發了月餅,嗯~時間來不及了,上週應該搞個活動抽中幾個粉絲髮月餅的,下次吧,這裡先預告一下,聖誕節活動,給粉絲送蘋果吧哈哈,不過聽起來好 low 呀,大家有好的想法可以下邊評論或者來群裡一起交流喲~

說接上文,昨天咱們第一次的接觸到了一個新的框架 Nuxt《二七║ Nuxt 基礎:框架初探》,從概念上,給大家簡單說了下這個框架的產生和應用場景,大家學習這一塊一定要有一定的 vue 基礎,還有就是了解 SSR 服務端渲染的知識和原理,才能做到遊刃有餘,在之前的一系列文章中,咱們已經很詳細說明了 vue 的客戶端渲染 SPA 的工作原理,今天呢,咱們就重點說一下 Nuxt 的執行機制,雖說 Vue 已經發展了近兩年,不過網上的資料還是比較匱乏,所有的教程也都是一篇搞定,直接就是 如何安裝,如何寫頁面,我是不喜歡這樣的,雖然自己懂得的也不是很多,但是還是想給大家分享下,至少往深層次挖掘一下,這樣大家下次使用的時候,心裡才有一個普。好啦,馬上開始今天的講解,今天沒有涉及到程式碼,原始碼會在下週開始 Code 。

零、今天要完成綠色的部分

一、Nuxt 的初衷 —— 將核心專注於UI渲染

來個官方的說法

Nuxt.js is a minimalistic framework for server-rendered Vue applications (inspired by Next.js)

意思就是:

Nuxt 是伺服器呈現的簡約應用程式的框架,通過對客戶端和服務端基礎架構的抽象,Nuxt.js 可以讓開發者更專注於頁面的UI渲染。作用就是在 node.js 上進一步封裝,然後省去我們搭建服務端環境的步驟,只需要遵循這個庫的一些規則就能輕鬆實現 SSR。

說到這裡大家可能稍微不是很明白,舉個最簡單的栗子( 這裡就不說那個老生常談的 SEO 了,這個本來就不存在於我們的客戶端渲染中 ):咱們之前在用 vue-cli 腳手架搭建專案的時候,每次新增一個頁面,都需要去配置路由 router ,是吧,然後呢,有時候運氣不好了,在多層巢狀的路徑配置時候,還時不時的不起作用,這些本來不應該屬於我們開發的工作的,Nuxt 就很好的解決了這個問題,Nuxt.js 根據 pages 目錄結構去生成 vue-router 配置,也就是說 pages 目錄的結構直接影響路由結構,還是實時監聽的方式。

再舉個栗子:之前咱們在配置頁面的時候,都是通過 index.html 提供一個模板,然後再一個 app.vue 的入口程式,然後將所有的 *.vue 元件通過路由填充進去,這個咱們第一次使用的時候,感覺還是很嗨皮的,不過長時間的使用的時候,就會發現其實有些問題,比如我們如果不想讓某些元件路由顯示出來,然後只能在配置裡處理,通過配置路由是否顯示等操作,顯然又多了一個步驟,不過在 nuxt 框架中,我們有了動態路由,只需要在名字前加上一個下劃線 _ ,比如 _id.vue ,這樣就能輕鬆搞定,是不是很方便,這個咱們以後會講到,這裡暫時就不深入了。

再比如我們在之前說到元件的時候,用到了這個圖,在入門頁面中,將我們的所有元件按需載入,但是如果我們的專案有多個佈局需要怎麼辦?比如我們商城首頁,和個人中心首頁有時候是不一樣的,當時如果你一定要保持一個風格也是可以的,但是感覺這麼配置還是怪怪的。

不過在 Nuxt.js 框架中,我們有了新的變化,layouts對應目錄中的layouts資料夾,預設pages下的頁面走的都是 layouts/default.vue 佈局方式,但是如果自己定義的話,也可以新增新的佈局頁。其中<nuxt/>可以類似vueslot插槽的概念,(如果對 slot 插槽不是很明白,可以看之前的文章 《二十║Vue基礎終篇:元件詳解+專案說明》的第四節),pages/**.vue中的內容會插在<nuxt/>內。

在咱們的第二個專案,基於 Nuxt 的個人部落格裡,我就新建了一個 blog.vue 的佈局頁面,定製了一個部落格佈局:

<template>
  <div class="layout-default">
      <cl-header></cl-header>//定製部落格頁頭
      <nuxt class="layout-main"/>//nuxt 插入內容
      <cl-footer></cl-footer>//定製部落格頁尾
      <div class="layout-bg"></div>
  </div>
</template>

<script type="text/javascript">
import clHeader from "~/components/layout/header.vue";
import clFooter from "~/components/layout/footer.vue";
export default {
    data () {
        return {};
    },
    mounted () {
        this.$store.dispatch("initUser");
    },
    components: {
        clHeader,
        clFooter
    }
};
</script>

這個就是 nuxt 的分層頁面佈局。

由上邊的栗子可以看出來,nuxt 是通過封裝,將核心原理隱藏起來,重點實現我們的UI展示,這麼做的目的就是更有經歷去做 SSR 服務端渲染了,大家還記得咱們在講 SSR 的時候麼,需要很複雜的操作配合才能實現,所以,很好的底層封裝,是我們更好開發 SSR 框架的第一步。

二、Nuxt 的工作流程 —— 四步走

還是老規矩,一言不合就放圖系列

簡單來說,當你訪問一個基於Nuxt.js構建的頁面時,發生了的事情如下:

1、當用戶訪問應用程式, 如果 store 中定義了 nuxtServerInit action,Nuxt.js 將呼叫它更新 store。

2、將載入即將訪問頁面所依賴的任何中介軟體。Nuxt 首先從 nuxt.config.js 這個檔案中,載入全域性依賴的中介軟體,之後檢測每個相應頁面對應的佈局檔案 ,最後,檢測佈局檔案下子元件依賴的中介軟體。以上是中介軟體的載入順序。

3、如果要訪問的路由是一個動態路由, 且有一個相應的 validate() 方法路由的validate 方法,講進行路由校驗。

4、Nuxt.js 呼叫 asyncData() 和 fetch() 方法,在渲染頁面之前載入非同步資料。asyncData() 方法用於非同步獲取資料,並將 fetch 回來的資料,在服務端渲染到頁面。 用 fetch() 方法取回的將資料在渲染頁面之前填入store。

最後一步, 將所有資料渲染到頁面。

其中核心配置檔案 nuxt.config.js 是比較重要的一個檔案,整個系統的配置和外掛都需要這個檔案來執行,感覺就像是我們 vue-cli 腳手架裡的 main.js 入口檔案一樣,具體的配置以及使用咱們在以後的文章中再說明吧。上邊咱們說到了還是工作流程,那 nuxt 到底封裝了怎麼的工作機制,才能很好的執行這個流程呢,重頭戲來了

三、面向原始碼簡要分析 Nuxt 的工作機制

1、通過 .nuxt 檔案來執行我們的工作流程

 剛剛咱們說到了,nuxt.config.js 就像是一個 main.js 入口一樣,這個時候細心的你一定會發現,欸?為什麼 nuxt 框架沒有 router 呢,那我們的路由該如何配置呢,不要著急,nuxt 已經給我們封裝了,我們無需把注意力放到這上邊,這個時候我們執行 

npm run dev

會發現多了一個檔案(因為昨天我們已經執行過了,現在就不需要執行了)。

 

這個是我們專案生成的臨時檔案,我們專案執行時候配置的檔案都是在這裡,大家可以看到這裡的路由檔案,沒錯,這個就是系統自動給我們配置的路由檔案,根據我們的 pages 資料夾路徑生成的,大家還可以看到,由app.js ,client.js 和 server.js 這兩個就是類似我們的 SSR 中配置的那個 server.js 入口檔案,然後還有 middleware.js 中介軟體檔案,其實這個時候我們大概能懂了,上邊我們說的工作流程,走的就是這個 臨時檔案.nuxt 資料夾中的內容,但是這個資料夾是如何生成的呢,大家請往下看。

2、.nuxt 是如何產生的

本文主要研究nuxt的執行原理,分析它從接收一條nuxt指令,到完成指令背後所發生的一系列事情,在開始本文之前,請讀者務必親自體驗過nuxt.js的使用,並且具備一定的vue.js相關開發經驗。

通過檢視nuxt.js工程目錄下的package.json檔案,我們可以看到下列幾條指令:

"scripts": { 
    "dev": "nuxt",           //    開啟一個監聽3000埠的伺服器,同時提供hot-reloading功能
    "build": "nuxt build", //構建整個應用,壓縮合並JS和CSS檔案(用於生產環境)
    "start": "nuxt start", //    開啟一個生產模式的伺服器(必須先執行nuxt build命令)
    "generate": "nuxt generate" //構建整個應用,併為每一個路由生成一個靜態頁面(用於靜態伺服器)
} 

咱們還從來沒有看過我們的依賴包哈,今天就來看看,開啟我們的 node_modules 資料夾下的 nuxt工程資料夾 進入到到bin目錄,我們可以看到5個檔案:

咱們就說一下 dev 是如何工作的,咱們先找到一個片段,發現基本是執行了以下幾個步驟:

try {
    nuxt = new Nuxt(options)//例項化一個 nuxt 類
    builder = new Builder(nuxt)
    instance = { nuxt, builder }
  } 。。。。

  return (
    Promise.resolve()
      .then(
        () =>
          oldInstance && oldInstance.builder
            ? oldInstance.builder.unwatch()
            : Promise.resolve()
      )
      // Start build 開始執行 build() 方法
      .then(() => builder.build())
      // Close old nuxt after successful build
      .then(
        () =>
          oldInstance && oldInstance.nuxt
            ? oldInstance.nuxt.close()
            : Promise.resolve()
      )
      // Start listening 開啟監聽服務埠
      .then(() => nuxt.listen(port, host))
      // Pass new nuxt to watch chain
      .then(() => instance)
      // Handle errors
      .catch(err => onError(err, instance))
  )
}

3、那什麼是 nuxt() 類,它又是執行了什麼樣的方法呢?

這個時候,我們繼續看原始碼,進入到nuxt/lib目錄,我們可以看到如下的檔案目錄結構:

├── app                       
├── builder                     
├── common                      
└── core                        
     ├── middleware                           
     │   ├── index.js             
     │   ├── meta.js             
     │   ├── module.js          
     │   ├── muxt.js           
     │   └── render.js          
     └── index.js                

 大家看這個熟悉不熟悉?!沒錯,就是和我們上邊專案執行的時候生成的臨時檔案是類似的,請和上邊的 .nuxt 臨時資料夾做對比。

 網上有個很好的總結,如圖:

上圖中每一步都可以在具體的程式碼中自行瀏覽。在使用者輸入指令並例項化了Nuxt()類以後,例項化出來的nuxt物件就會執行圖中打了綠色對勾的幾個方法:build(), render(), renderRoute(), renderAndGetWindow()以及generate()方法。

同時,Nuxt()類也提供了一個close()公有方法,用於關閉其所開啟的伺服器。

4、build() 進行編譯,生成 .nuxt 臨時檔案

然後就是執行的 build() 方法:

簡單來說,build()方法在判斷完執行條件後,會先初始化產出目錄 .nuxt ,然後通過不同目錄下的檔案結構來生成一系列的配置,寫入模板檔案後輸出到.nuxt目錄。接下來,則會根據不同的開發環境來呼叫不同的webpack 配置,執行不同的 webpack 構建方案。

5、render.js檔案 打包輸出渲染

在 nuxt/lib/core 目錄下找到 render.js 檔案,它包含著我們即將要分析的三個方法:render(), renderRoute(), renderAndGetWindow()。

通過這張圖片,我們可以知道nuxt對於處理“客戶端渲染”與“服務端渲染”的邏輯其實是非常清晰的。

首先,在render()方法在處理完一系列的路徑問題後,會呼叫renderRoute()方法,獲取響應所需內容並完成響應。

其中renderRoute()方法會判斷當前響應是否應執行服務端渲染。如果是,則呼叫vue提供的bundleRenderer()方法,把html內容渲染完畢以後再整體輸出;如果不是,則直接輸出一個<div></div>字串,交由客戶端渲染。

 // Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
    let APP = await this.bundleRenderer.renderToString(context)

    if (!context.nuxt.serverRendered) {
      APP = '<div id="__nuxt"></div>'
    }

最後,通過renderAndGetWindow()來檢查輸出的html是否存在問題,然後發出通知,表明html可用。

 四、結語

 好啦,今天因為時間的問題,暫時就說到這裡吧,其實這裡還有很多知識點沒有說到,那我們就在之後的專案中,慢慢給大家說吧,請持續關注喲,可以點個贊