從壹開始前后端分離 [ vue + .netcore 補充教程 ] 三十║ Nuxt實戰:動態路由+同構

上期回顧

說接上文《二九║ Nuxt實戰:異步實現數據雙端渲染》,昨天咱們通過項目二的首頁數據處理,簡單了解到了 nuxt 異步數據獲取的作用,以及親身體驗了幾個重要頁面的意義,整篇文章也一直在往如何實現服務端渲染的方向講解,因為我個人感覺這個是一個重點,如果是只會如何使用的話,大家就可以走馬觀花的看看就行了,昨天呢,遺留了幾個問題,我也想了想,還沒有想好如何通過淺顯的話來概括,如果要是搬出來教科書似的講解,感覺又不是很清晰,我就在以后的領悟中補充吧,這里就先說下其中的三個問題:

1、我們通過 dev 編譯,生成的 .nuxt 臨時文件夾(我個人感覺他就像我們 .net core 中的 bin 文件夾),.nuxt 目錄為 npm run dev或者是npm run build 后才生成,兩個操作都執行了 build() 方法,用于存放 Nuxt.js 的核心庫文件,如果你將一個老項目的 .nuxt 文件夾覆蓋一個新項目的 .nuxt 文件夾,新項目正常運行,按照老的項目路由規則之類的都可以正常訪問。例如,你可以在這個目錄下找到 server.js 文件,描述了 Nuxt.js 進行服務端渲染的邏輯,流程是:調用 nuxtServerInit 方法,當請求打入時,最先調用的即是 nuxtServerInit 方法,可以通過這個方法預先將服務器的數據保存,如已登錄的用戶信息等。另外,這個方法中也可以執行異步操作,并等待數據解析后返回。Middleware 層,經過第一步后,請求會進入 Middleware 層,在該層中有三步操作:讀取 nuxt.config.js 中全局 middleware 字段的配置,并調用相應的中間件方法 匹配并加載與請求相對應的 layout 調用 layoutpage 的中間件方法。調用 validate 方法,在這一步可以對請求參數進行校驗,或是對第一步中服務器下發的數據進行校驗,如果校驗失敗,將拋出 404 頁面。

調用 fetchasyncData 方法,這兩個方法都會在組件加載之前被調用,它們的職責各有不同, asyncData 用來異步的進行組件數據的初始化工作,而 fetch 方法偏重于異步獲取數據后修改 Vuex 中的狀態。


2、每次修改文件,都會觸發熱 webpack 的[HMR] 熱加載,因為 Nuxt.js集成了如下模塊: Vue-Router, Vue-Meta 和 Vuex (僅在使用 Vuex 狀態樹配置項 時引入)。 這樣的好處在于,不需要手工配置依賴,每次當我們修改文件,webpack 就會自動保存,Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 來處理代碼的自動化構建工作(如打包、代碼分層、壓縮等等)。

image

4、在 network 中,當有一個請求過來時,服務器會新建一個vue實例,渲染(render)出需要顯示的頁面的html,把得到的頁面以字符串的形式返回給客戶端。同時把相關的js文件也返回(首次請求時返回vue的runtime、webpack的runtime和app.js等文件,非首次請求返回按需加載的js文件),返回的js文件和單頁面應用(SPA)返回的差不多

app.js:基本就是你實際編寫的那個app.vue(.vue或.js),沒這個頁面跑不起來,該頁面應該提供了跟app應用相關的公共方法,腳本里也明確配置了跟路由相關的信息

vendor.js:vue-cli全家桶默認配置里面這個chunk就是將所有從node_modules/里require(import)的依賴都打包到這里,所以這個就是所有node_modules/下的被require(import)的js文件

manifest.js: 最后一個chunk,被注入了webpackJsonp的定義及異步加載相關的定義(webpack調用CommonsChunkPlugin處理后模塊管理的核心,因為是核心,所以要第一個進行加載,不然會報錯),該文件確定是跟路由相關的配置信息,其中明確包含了路由的路徑,和版本號,但是暫時不明白為何前端輸出會保留該配置(大概是做一些頁面動態切換效果或者是預加載的時候使用,但是頁面的預加載已經在ssr 輸出的html 已經包含了)

然后還有一些 pages_index.js文件,布局 layouts_blog.js文件等:default.js(跟dis/layout/default.js一致,是載入了使用的layout)

。瀏覽器接收到這些文件后,通過js文件把靜態頁面的字符串hydrate成可以交互的應用。和SPA相比,SSR返回的數據就是多了個靜態頁面(字符串形式)。

image

我又一次老生常談的說了一遍,還是感覺不是很清晰,看來自己的功底還是不行呀,如果有愛好 nuxt 或者 做過 SSR 的小伙伴,歡迎聯系,咱們一起討論下,今天呢,接著昨天的工作,把詳情頁渲染出來吧~~~

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

image

一、動態路由實現詳情頁布局設計

經過昨天的首頁渲染,大家不知道使用起來怎么樣,不僅可以配置每一頁的 head 信息( TDK head),還可以對整體進行配置,雖然中間引入了 plugins 插件機制,不過也是很好的做了封裝,特別是路由這一塊,大家是不是發現已經完全不用配置了,Nuxt.js 依據 pages 目錄結構自動生成 vue-router 模塊的路由配置,為我們減少了很大的工作量,今天咱們就繼續對詳情頁進行配置。

什么是動態路由

昨天呢,咱們開發了首頁,通過地址直接可以訪問,但是在開發過程中,肯定會有這樣的頁面:通過不同的 id 加載不同的詳情頁面,這些頁面雖然是一個,但是 URL 地址卻是多個,所以我們就說這個路由是動態的,還記得咱們在第一個項目中的時候,是怎么配置的么?我們通過頁面接收參數來實現動態路由

 {
      path: "/Content/:id",
      name: "Content",
      component: Content
    },
image

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

以下目錄結構:

pages/
--| _slug/
-----| comments.vue -----| index.vue --| users/
-----| _id.vue --| index.vue

Nuxt.js 生成對應的路由配置表為:

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

你會發現名稱為 users-id 的路由路徑帶有 :id? 參數,表示該路由是可選的。如果你想將它設置為必選的路由,需要在 users/_id 目錄內創建一個 index.vue 文件。

添加博客詳情頁

1、在 pages 文件夾中,添加 blog 文件夾,然后添加 _id.vue 頁面

這個時候,我們看我們的臨時編譯文件 .nuxt 中 router.js 已經動態的增加上了上邊添加的路由

 return new Router({
    mode: 'history', base: '/',
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [
        {
            path: "/blog/:id?",
            component: _66cb1a63,
            name: "blog-id" },
        {
            path: "/",
            component: _70e72bdd,
            name: "index" }
    ],
image

2、編輯 _id.vue 文件,實現數據獲取

<template>
  <div class="post-page">
    <h1 class="title">{{data.btitle}}</h1>
    <p class="createTime">{{data.bCreateTime }}</p>
    <div  v-html="data.bcontent"  ></div>
  </div>
</template>

<script> import Vue from "vue";
  export default {
    layout: "blog",
    validate ({ params }) { // 校驗文章id是否為數字
      return /^\d+$/.test(params.id);
    }, async asyncData ({ params, error }) { // 獲取文章詳情
      let data = {}; try {
        data = await Vue.http.get(`blog/${params.id}`); return {
          data: data
        };
      } catch (e) { //error({ statusCode: 404, message: "出錯啦" });
 }
    },
    fetch ({ store, params }) {},
    data () { return {
        comments: []
      };
    },
    head () {//設置頁面 head 信息
      return {
        title: `${this.data.btitle}`,
        meta: [
          {
            name: "description",
            content: this.data.btitle
          }
        ]
      };
    },
    filters: {
      timeFormat: function (time) { if (!time) return ""; return time;
      }
    },
    mounted () {},
    components: {
    }
  }; </script>
//導入樣式
<style lang="css"> @import "../../static/vue-blog-sq.css"; </style>

是不是很簡單,直接添加頁面內容,就可以實現路由渲染,直接就可以訪問了,不過這里可能會有一個坑,如果你運氣好的話,會碰上,運氣不好,就過去了。

3、刷新頁面查看結果

image

蒼天呀,不是吧,報錯了?!如果你看到這個錯誤,恭喜你比較幸運,可能你會進一步的了解到 nuxt 是如何渲染的。

4、點擊 瀏覽器后退 ,返回到首頁,發現更加崩潰

image

不僅剛剛的詳情頁不見了,就連我們的首頁數據也出錯了!雖然這上面有數據,但是這個是瀏覽器緩存的,而不是我們真實的數據,這個時候著急的小伙伴,一定會很著急,穩住,我們能贏!

首次運行服務端渲染,然后開始客戶端渲染

這個時候,如果你刷新首頁,發現一切正常,不僅如何,如果你刷新詳情頁,數據也能出現,不信你可以試試,那這是為什么呢?

原因就在于我們刷新頁面,或者新窗口打開等等,都是新開了一個服務,我們的頁面為了實現 SEO 先進行的是服務端渲染,講整個頁面的字符串發送過來,然后點擊鏈接去詳情頁的時候,我們就開始走客戶端渲染了,之所以頁面會報錯,就是我們存在跨域的問題。

你可能會問,問什么第一次不存在,因為第一次是服務端渲染呀,服務端是不存在跨域問題的,只有 js 請求才會存在跨域的問題,到這里,通過這個錯誤你是不是了解到了一點兒,這個錯誤也是我故意放出來的,就是為了讓大家更清楚的了解到 nuxt 是如何進行渲染的。這也能說的通,為什么第一次刷新首頁有數據,從詳情頁返回過來,報錯的原因了,因為第二次渲染已經交給客戶端了。

image

解決辦法很簡單,還是在我們 .net core api 中 CORS 跨域配置我們的端口就行,然后一切正常了。

image

相信這個時候你對 nuxt 的渲染有了一點理解了吧,如果還不是很清晰,請往下看

二、SSR 同構知多少

SSR 用通過同構的方法解決了上面問題。我們先說一下 SSR 的具體表現,比如我們現在有一個列表頁,列表中每一行對應一個詳情頁,那么如果直接用瀏覽器訪問列表頁時,服務器返回數據和 html 融合后的頁面,瀏覽器拿到頁面直接渲染,這就省去了先請求 js 再由 js 發起數據請求的過程,頁面渲染的同時請求js,js加載完成后綁定事件;從列表頁中點擊某一條到詳情頁的時候,和普通的全棧 Ajax 一樣,先請求 js 再由 js 發起數據請求,然后填充數據渲染頁面。如果將詳情頁的鏈接復制出來,直接在新瀏覽中訪問,那么詳情頁會直接返回數據和 html 融合后的頁面(服務端渲染),渲染的同時請求詳情頁 js,最后再綁定事件。這個“服務器端拼接 html 和 html 是由同樣的頁面和組件完成的,這種前后端采用同樣的結構在不同的環境中產出同樣的 html 的方案稱之為“同構”。

什么叫前后端同構?

為了解決某些問題(比如SEO、提升渲染速度等)vue 提供了2個方法在服務端生成一個HTML文本格式的字符串。在得到了這個HTML格式的字符串之后,通常會將其組裝成一個頁面直接返回給用戶的瀏覽器。

到這里,服務端的活已經干完了,然后就是瀏覽器這邊干活。

瀏覽器拿到HTML文本后,立刻進行渲染將內容呈現給用戶。然后加載頁面所需的 .js 文件,然后執行 JavaScript 腳本,然后開始初始化 vue 組件

到這里問題就來了。vue 初始化組件后會執行組件內所有 render () 方法,然后生成虛擬DOM的樹形結構,然后在適當的時候將虛擬dom寫到瀏覽器的真實 dom 中。因為 vue 總是根據虛擬 dom 來生成真實dom,所以最后會把服務器端渲染好的HTML全部替換掉。

上面這個事情說不是問題確實也不是問題,無非就是用戶看到頁面然后“閃現”一下。說是問題還真是個問題,產品會拿著這毛病從用戶體驗的角度在各種場合和你死磕半個月。磕累了你索性把服務端渲染關了,然后運營又拿著SEO的問題準備和你開始撕逼了。

為了解決這些問題,他們在 .renderToString(element) 方法中提供了一個 checksum 機制。前后端同構就是保證前端和后端的dom結構一致,不會發生重復渲染。

什么叫 首屏渲染?

簡單的說就是 vue 在瀏覽器內存中第一次生成的虛擬 dom 樹。切記是虛擬 dom ,而不是瀏覽器的dom。

了解 vue 的應該知道,所有 vue組件都有一個 render() 方法(如果使用function方式編寫的組件會把function里的所有代碼都塞到 render() 方法中去)。當 render( element, container, [callback] )方法執行時,會執行以下步驟:

1. 所有組件的會先進行初始化(es6執行構造函數)。
2. 所有組件的 render () 方法會被調用一次,完成這個過程后會得到一顆虛擬的 dom 樹。
3. vue 會將虛擬dom轉換成瀏覽器dom,完成后調用組件的 componentDidMount() 方法告訴你已經裝載到瀏覽器上了。

在上面這個過程成中,步驟2完成后即為完成 vue 的首屏渲染。結合 checksum 機制步驟3有可能不會執行。

當組件狀態發生變更時( setState() 生命周期函數被調用)或者 父組件渲染時(父組件的 render() 方法被調用),當前組件的 render() 方法都會被執行,都有可能會導致虛擬dom變更,但是這些變更和首屏渲染沒任何關系了。

在我們的項目中,查看是如何渲染的

1、在我們的首頁中,首次加載,在 network 中,查看我們都加載了那些文件

image

這些文件咱們在文章頂部都講到了,這里說下 初始頁面,它是直接將 html 返回給我們的前端渲染,這個很好理解

2、點擊到詳情頁

image

我們發現這個我們的網絡請求,并沒有繼續打包 build 走服務端渲染,而是僅僅請求了一個接口,返回了 json 數據,從這里大家應該就能看的處理,這就是所謂的雙端渲染模式。

三、總結

好啦,今天就暫時說到這里了,通過詳情頁的添加,大家會切身體會到 nuxt 的渲染模式,是如何在服務端和客戶端之間來回切換渲染的,這三篇文章大家要多看看,才能了解其中的內涵,加油鴨~~

四、Github

https://github.com/anjoy8/Blog.Vue.Nuxt

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容