從壹開始前后端分離 [ 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可用。

 

 四、結語

 好啦,今天因為時間的問題,暫時就說到這里吧,其實這里還有很多知識點沒有說到,那我們就在之后的項目中,慢慢給大家說吧,請持續關注喲,可以點個贊👍吧,真的是辛苦哈哈哈

 


免責聲明!

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



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