說接上文《二九║ Nuxt實戰:異步實現數據雙端渲染》,昨天我們經過項目二的首頁數據處理,簡單瞭解到了 nuxt 異步數據獲取的做用,以及親身體驗了幾個重要文件夾的意義,整篇文章也一直在往如何實現服務端渲染的方向講解,由於我我的感受這個是一個重點,若是是隻會如何使用的話,你們就能夠蜻蜓點水的看看就好了,昨天呢,遺留了幾個問題,我也想了想,尚未想好如何經過淺顯的話來歸納,若是要是搬出來教科書似的講解,感受又不是很清晰,我就在之後的領悟中補充吧,這裏就先說下其中的三個問題:css
一、咱們經過 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
調用 layout
和 page
的中間件方法。調用 validate
方法,在這一步能夠對請求參數進行校驗,或是對第一步中服務器下發的數據進行校驗,若是校驗失敗,將拋出 404 頁面。html
調用 fetch
及 asyncData
方法,這兩個方法都會在組件加載以前被調用,它們的職責各有不一樣, asyncData
用來異步的進行組件數據的初始化工做,而 fetch
方法偏重於異步獲取數據後修改 Vuex 中的狀態。前端
二、每次修改文件,都會觸發熱 webpack 的[HMR] 熱加載,由於 Nuxt.js集成了以下模塊: Vue-Router, Vue-Meta 和 Vuex (僅在使用 Vuex 狀態樹配置項 時引入)。 這樣的好處在於,不須要手工配置依賴,每次當咱們修改文件,webpack 就會自動保存,Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 來處理代碼的自動化構建工做(如打包、代碼分層、壓縮等等)。vue
四、在 network 中,當有一個請求過來時,服務器會新建一個vue實例,渲染(render)出須要顯示的頁面的html,把獲得的頁面以字符串的形式返回給客戶端。同時把相關的js文件也返回(首次請求時返回vue的runtime、webpack的runtime和app.js等文件,非首次請求返回按需加載的js文件),返回的js文件和單頁面應用(SPA)返回的差很少node
app.js:基本就是你實際編寫的那個app.vue(.vue或.js),沒這個頁面跑不起來,該頁面應該提供了跟app應用相關的公共方法,腳本里也明確配置了跟路由相關的信息webpack
vendor.js:vue-cli全家桶默認配置裏面這個chunk就是將全部從node_modules/裏require(import)的依賴都打包到這裏,因此這個就是全部node_modules/下的被require(import)的js文件git
manifest.js: 最後一個chunk,被注入了webpackJsonp的定義及異步加載相關的定義(webpack調用CommonsChunkPlugin處理後模塊管理的核心,由於是核心,因此要第一個進行加載,否則會報錯),該文件肯定是跟路由相關的配置信息,其中明確包含了路由的路徑,和版本號,可是暫時不明白爲什麼前端輸出會保留該配置(大概是作一些頁面動態切換效果或者是預加載的時候使用,可是頁面的預加載已經在ssr 輸出的html 已經包含了)es6
而後還有一些 pages_index.js文件,佈局 layouts_blog.js文件等:default.js(跟dis/layout/default.js一致,是載入了使用的layout)github
。瀏覽器接收到這些文件後,經過js文件把靜態頁面的字符串hydrate成能夠交互的應用。和SPA相比,SSR返回的數據就是多了個靜態頁面(字符串形式)。web
我又一次老生常談的說了一遍,仍是感受不是很清晰,看來本身的功底仍是不行呀,若是有愛好 nuxt 或者 作過 SSR 的小夥伴,歡迎聯繫,我們一塊兒討論下,今天呢,接着昨天的工做,把詳情頁渲染出來吧~~~
通過昨天的首頁渲染,你們不知道使用起來怎麼樣,不只能夠配置每一頁的 head 信息( TDK head),還能夠對總體進行配置,雖然中間引入了 plugins 插件機制,不過也是很好的作了封裝,特別是路由這一塊,你們是否是發現已經徹底不用配置了,Nuxt.js 依據 pages
目錄結構自動生成 vue-router 模塊的路由配置,爲咱們減小了很大的工做量,今天我們就繼續對詳情頁進行配置。
昨天呢,我們開發了首頁,經過地址直接能夠訪問,可是在開發過程當中,確定會有這樣的頁面:經過不一樣的 id 加載不一樣的詳情頁面,這些頁面雖然是一個,可是 URL 地址倒是多個,因此咱們就說這個路由是動態的,還記得我們在第一個項目中的時候,是怎麼配置的麼?咱們經過頁面接收參數來實現動態路由
{ path: "/Content/:id", name: "Content", component: Content },
在 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
文件。
一、在 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" } ],
二、編輯 _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>
是否是很簡單,直接添加頁面內容,就能夠實現路由渲染,直接就能夠訪問了,不過這裏可能會有一個坑,若是你運氣好的話,會碰上,運氣很差,就過去了。
三、刷新頁面查看結果
蒼天呀,不是吧,報錯了?!若是你看到這個錯誤,恭喜你比較幸運,可能你會進一步的瞭解到 nuxt 是如何渲染的。
四、點擊 瀏覽器後退 ,返回到首頁,發現更加崩潰
不只剛剛的詳情頁不見了,就連咱們的首頁數據也出錯了!雖然這上面有數據,可是這個是瀏覽器緩存的,而不是咱們真實的數據,這個時候着急的小夥伴,必定會很着急,穩住,咱們能贏!
這個時候,若是你刷新首頁,發現一切正常,不只如何,若是你刷新詳情頁,數據也能出現,不信你能夠試試,那這是爲何呢?
緣由就在於咱們刷新頁面,或者新窗口打開等等,都是新開了一個服務,咱們的頁面爲了實現 SEO 先進行的是服務端渲染,講整個頁面的字符串發送過來,而後點擊連接去詳情頁的時候,咱們就開始走客戶端渲染了,之因此頁面會報錯,就是咱們存在跨域的問題。
你可能會問,問什麼第一次不存在,由於第一次是服務端渲染呀,服務端是不存在跨域問題的,只有 js 請求才會存在跨域的問題,到這裏,經過這個錯誤你是否是瞭解到了一點兒,這個錯誤也是我故意放出來的,就是爲了讓你們更清楚的瞭解到 nuxt 是如何進行渲染的。這也能說的通,爲何第一次刷新首頁有數據,從詳情頁返回過來,報錯的緣由了,由於第二次渲染已經交給客戶端了。
解決辦法很簡單,仍是在咱們 .net core api 中 CORS 跨域配置咱們的端口就行,而後一切正常了。
相信這個時候你對 nuxt 的渲染有了一點理解了吧,若是還不是很清晰,請往下看
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變動,可是這些變動和首屏渲染沒任何關係了。
一、在咱們的首頁中,首次加載,在 network 中,查看咱們都加載了那些文件
這些文件我們在文章頂部都講到了,這裏說下 初始頁面,它是直接將 html 返回給咱們的前端渲染,這個很好理解
二、點擊到詳情頁
咱們發現這個咱們的網絡請求,並無繼續打包 build 走服務端渲染,而是僅僅請求了一個接口,返回了 json 數據,從這裏你們應該就能看的處理,這就是所謂的雙端渲染模式。
好啦,今天就暫時說到這裏了,經過詳情頁的添加,你們會切身體會到 nuxt 的渲染模式,是如何在服務端和客戶端之間來回切換渲染的,這三篇文章你們要多看看,才能瞭解其中的內涵,加油鴨~~