上期回顧
說接上文《二九║ Nuxt實戰(zhàn):異步實現(xiàn)數(shù)據(jù)雙端渲染》编兄,昨天咱們通過項目二的首頁數(shù)據(jù)處理桐经,簡單了解到了 nuxt 異步數(shù)據(jù)獲取的作用契吉,以及親身體驗了幾個重要頁面的意義漠其,整篇文章也一直在往如何實現(xiàn)服務端渲染的方向講解,因為我個人感覺這個是一個重點瑞驱,如果是只會如何使用的話,大家就可以走馬觀花的看看就行了窄坦,昨天呢唤反,遺留了幾個問題,我也想了想鸭津,還沒有想好如何通過淺顯的話來概括彤侍,如果要是搬出來教科書似的講解,感覺又不是很清晰逆趋,我就在以后的領(lǐng)悟中補充吧盏阶,這里就先說下其中的三個問題:
1、我們通過 dev 編譯闻书,生成的 .nuxt 臨時文件夾(我個人感覺他就像我們 .net core 中的 bin 文件夾)名斟,.nuxt 目錄為 npm run dev或者是npm run build 后才生成,兩個操作都執(zhí)行了 build() 方法魄眉,用于存放 Nuxt.js 的核心庫文件砰盐,如果你將一個老項目的 .nuxt 文件夾覆蓋一個新項目的 .nuxt 文件夾,新項目正常運行坑律,按照老的項目路由規(guī)則之類的都可以正常訪問岩梳。例如,你可以在這個目錄下找到 server.js
文件,描述了 Nuxt.js 進行服務端渲染的邏輯冀值,流程是:調(diào)用 nuxtServerInit
方法也物,當請求打入時,最先調(diào)用的即是 nuxtServerInit
方法列疗,可以通過這個方法預先將服務器的數(shù)據(jù)保存滑蚯,如已登錄的用戶信息等。另外作彤,這個方法中也可以執(zhí)行異步操作膘魄,并等待數(shù)據(jù)解析后返回。Middleware
層竭讳,經(jīng)過第一步后创葡,請求會進入 Middleware
層,在該層中有三步操作:讀取 nuxt.config.js
中全局 middleware
字段的配置绢慢,并調(diào)用相應的中間件方法 匹配并加載與請求相對應的 layout
調(diào)用 layout
和 page
的中間件方法灿渴。調(diào)用 validate
方法,在這一步可以對請求參數(shù)進行校驗胰舆,或是對第一步中服務器下發(fā)的數(shù)據(jù)進行校驗骚露,如果校驗失敗,將拋出 404 頁面缚窿。
調(diào)用 fetch
及 asyncData
方法棘幸,這兩個方法都會在組件加載之前被調(diào)用,它們的職責各有不同倦零, asyncData
用來異步的進行組件數(shù)據(jù)的初始化工作误续,而 fetch
方法偏重于異步獲取數(shù)據(jù)后修改 Vuex 中的狀態(tài)。
2扫茅、每次修改文件蹋嵌,都會觸發(fā)熱 webpack 的[HMR] 熱加載,因為 Nuxt.js集成了如下模塊: Vue-Router, Vue-Meta 和 Vuex (僅在使用 Vuex 狀態(tài)樹配置項 時引入)葫隙。 這樣的好處在于栽烂,不需要手工配置依賴,每次當我們修改文件恋脚,webpack 就會自動保存腺办,Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 來處理代碼的自動化構(gòu)建工作(如打包糟描、代碼分層菇晃、壓縮等等)。
4蚓挤、在 network 中磺送,當有一個請求過來時驻子,服務器會新建一個vue實例,渲染(render)出需要顯示的頁面的html估灿,把得到的頁面以字符串的形式返回給客戶端崇呵。同時把相關(guān)的js文件也返回(首次請求時返回vue的runtime、webpack的runtime和app.js等文件馅袁,非首次請求返回按需加載的js文件)域慷,返回的js文件和單頁面應用(SPA)返回的差不多
app.js:基本就是你實際編寫的那個app.vue(.vue或.js),沒這個頁面跑不起來,該頁面應該提供了跟app應用相關(guān)的公共方法汗销,腳本里也明確配置了跟路由相關(guān)的信息
vendor.js:vue-cli全家桶默認配置里面這個chunk就是將所有從node_modules/里require(import)的依賴都打包到這里犹褒,所以這個就是所有node_modules/下的被require(import)的js文件
manifest.js: 最后一個chunk,被注入了webpackJsonp的定義及異步加載相關(guān)的定義(webpack調(diào)用CommonsChunkPlugin處理后模塊管理的核心,因為是核心,所以要第一個進行加載,不然會報錯)弛针,該文件確定是跟路由相關(guān)的配置信息叠骑,其中明確包含了路由的路徑,和版本號削茁,但是暫時不明白為何前端輸出會保留該配置(大概是做一些頁面動態(tài)切換效果或者是預加載的時候使用宙枷,但是頁面的預加載已經(jīng)在ssr 輸出的html 已經(jīng)包含了)
然后還有一些 pages_index.js文件,布局 layouts_blog.js文件等:default.js(跟dis/layout/default.js一致茧跋,是載入了使用的layout)
慰丛。瀏覽器接收到這些文件后,通過js文件把靜態(tài)頁面的字符串hydrate成可以交互的應用瘾杭。和SPA相比诅病,SSR返回的數(shù)據(jù)就是多了個靜態(tài)頁面(字符串形式)。
我又一次老生常談的說了一遍粥烁,還是感覺不是很清晰贤笆,看來自己的功底還是不行呀,如果有愛好 nuxt 或者 做過 SSR 的小伙伴页徐,歡迎聯(lián)系,咱們一起討論下银萍,今天呢变勇,接著昨天的工作,把詳情頁渲染出來吧~~~
零贴唇、今天要完成紫色的部分
一搀绣、動態(tài)路由實現(xiàn)詳情頁布局設計
經(jīng)過昨天的首頁渲染,大家不知道使用起來怎么樣戳气,不僅可以配置每一頁的 head 信息( TDK head)链患,還可以對整體進行配置,雖然中間引入了 plugins 插件機制瓶您,不過也是很好的做了封裝麻捻,特別是路由這一塊纲仍,大家是不是發(fā)現(xiàn)已經(jīng)完全不用配置了,Nuxt.js 依據(jù) pages
目錄結(jié)構(gòu)自動生成 vue-router 模塊的路由配置贸毕,為我們減少了很大的工作量郑叠,今天咱們就繼續(xù)對詳情頁進行配置。
什么是動態(tài)路由
昨天呢明棍,咱們開發(fā)了首頁乡革,通過地址直接可以訪問,但是在開發(fā)過程中摊腋,肯定會有這樣的頁面:通過不同的 id 加載不同的詳情頁面沸版,這些頁面雖然是一個,但是 URL 地址卻是多個兴蒸,所以我們就說這個路由是動態(tài)的视粮,還記得咱們在第一個項目中的時候,是怎么配置的么类咧?我們通過頁面接收參數(shù)來實現(xiàn)動態(tài)路由
{
path: "/Content/:id",
name: "Content",
component: Content
},
在 Nuxt.js 里面定義帶參數(shù)的動態(tài)路由馒铃,需要創(chuàng)建對應的以下劃線作為前綴的 Vue 文件 或 目錄。
以下目錄結(jié)構(gòu):
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' } ]
}
你會發(fā)現(xiàn)名稱為 users-id
的路由路徑帶有 :id?
參數(shù)痕惋,表示該路由是可選的区宇。如果你想將它設置為必選的路由,需要在 users/_id
目錄內(nèi)創(chuàng)建一個 index.vue
文件值戳。
添加博客詳情頁
1议谷、在 pages 文件夾中,添加 blog 文件夾堕虹,然后添加 _id.vue 頁面
這個時候卧晓,我們看我們的臨時編譯文件 .nuxt 中 router.js 已經(jīng)動態(tài)的增加上了上邊添加的路由
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" }
],
2、編輯 _id.vue 文件赴捞,實現(xiàn)數(shù)據(jù)獲取
<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是否為數(shù)字
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>
是不是很簡單逼裆,直接添加頁面內(nèi)容,就可以實現(xiàn)路由渲染赦政,直接就可以訪問了胜宇,不過這里可能會有一個坑,如果你運氣好的話恢着,會碰上桐愉,運氣不好,就過去了掰派。
3从诲、刷新頁面查看結(jié)果
蒼天呀,不是吧靡羡,報錯了系洛?俊性!如果你看到這個錯誤,恭喜你比較幸運碎罚,可能你會進一步的了解到 nuxt 是如何渲染的磅废。
4、點擊 瀏覽器后退 荆烈,返回到首頁拯勉,發(fā)現(xiàn)更加崩潰
不僅剛剛的詳情頁不見了,就連我們的首頁數(shù)據(jù)也出錯了憔购!雖然這上面有數(shù)據(jù)宫峦,但是這個是瀏覽器緩存的,而不是我們真實的數(shù)據(jù)玫鸟,這個時候著急的小伙伴导绷,一定會很著急,穩(wěn)住屎飘,我們能贏妥曲!
首次運行服務端渲染,然后開始客戶端渲染
這個時候钦购,如果你刷新首頁檐盟,發(fā)現(xiàn)一切正常,不僅如何押桃,如果你刷新詳情頁葵萎,數(shù)據(jù)也能出現(xiàn),不信你可以試試唱凯,那這是為什么呢羡忘?
原因就在于我們刷新頁面,或者新窗口打開等等磕昼,都是新開了一個服務卷雕,我們的頁面為了實現(xiàn) SEO 先進行的是服務端渲染,講整個頁面的字符串發(fā)送過來票从,然后點擊鏈接去詳情頁的時候漫雕,我們就開始走客戶端渲染了,之所以頁面會報錯纫骑,就是我們存在跨域的問題蝎亚。
你可能會問九孩,問什么第一次不存在先馆,因為第一次是服務端渲染呀,服務端是不存在跨域問題的躺彬,只有 js 請求才會存在跨域的問題煤墙,到這里梅惯,通過這個錯誤你是不是了解到了一點兒,這個錯誤也是我故意放出來的仿野,就是為了讓大家更清楚的了解到 nuxt 是如何進行渲染的铣减。這也能說的通,為什么第一次刷新首頁有數(shù)據(jù)脚作,從詳情頁返回過來葫哗,報錯的原因了,因為第二次渲染已經(jīng)交給客戶端了球涛。
解決辦法很簡單劣针,還是在我們 .net core api 中 CORS 跨域配置我們的端口就行,然后一切正常了亿扁。
相信這個時候你對 nuxt 的渲染有了一點理解了吧捺典,如果還不是很清晰干旁,請往下看
二部念、SSR 同構(gòu)知多少
SSR 用通過同構(gòu)的方法解決了上面問題。我們先說一下 SSR 的具體表現(xiàn)厦画,比如我們現(xiàn)在有一個列表頁牍陌,列表中每一行對應一個詳情頁擎浴,那么如果直接用瀏覽器訪問列表頁時,服務器返回數(shù)據(jù)和 html 融合后的頁面呐赡,瀏覽器拿到頁面直接渲染退客,這就省去了先請求 js 再由 js 發(fā)起數(shù)據(jù)請求的過程,頁面渲染的同時請求js链嘀,js加載完成后綁定事件萌狂;從列表頁中點擊某一條到詳情頁的時候,和普通的全棧 Ajax 一樣怀泊,先請求 js 再由 js 發(fā)起數(shù)據(jù)請求茫藏,然后填充數(shù)據(jù)渲染頁面。如果將詳情頁的鏈接復制出來霹琼,直接在新瀏覽中訪問务傲,那么詳情頁會直接返回數(shù)據(jù)和 html 融合后的頁面(服務端渲染),渲染的同時請求詳情頁 js枣申,最后再綁定事件售葡。這個“服務器端拼接 html 和 html 是由同樣的頁面和組件完成的,這種前后端采用同樣的結(jié)構(gòu)在不同的環(huán)境中產(chǎn)出同樣的 html 的方案稱之為“同構(gòu)”忠藤。
什么叫前后端同構(gòu)挟伙?
為了解決某些問題(比如SEO、提升渲染速度等)vue 提供了2個方法在服務端生成一個HTML文本格式的字符串模孩。在得到了這個HTML格式的字符串之后尖阔,通常會將其組裝成一個頁面直接返回給用戶的瀏覽器贮缅。
到這里,服務端的活已經(jīng)干完了介却,然后就是瀏覽器這邊干活谴供。
瀏覽器拿到HTML文本后,立刻進行渲染將內(nèi)容呈現(xiàn)給用戶齿坷。然后加載頁面所需的 .js 文件桂肌,然后執(zhí)行 JavaScript 腳本,然后開始初始化 vue 組件
到這里問題就來了永淌。vue 初始化組件后會執(zhí)行組件內(nèi)所有 render () 方法轴或,然后生成虛擬DOM的樹形結(jié)構(gòu),然后在適當?shù)臅r候?qū)⑻摂Mdom寫到瀏覽器的真實 dom 中仰禀。因為 vue 總是根據(jù)虛擬 dom 來生成真實dom照雁,所以最后會把服務器端渲染好的HTML全部替換掉。
上面這個事情說不是問題確實也不是問題答恶,無非就是用戶看到頁面然后“閃現(xiàn)”一下饺蚊。說是問題還真是個問題,產(chǎn)品會拿著這毛病從用戶體驗的角度在各種場合和你死磕半個月悬嗓∥酆簦磕累了你索性把服務端渲染關(guān)了,然后運營又拿著SEO的問題準備和你開始撕逼了包竹。
為了解決這些問題燕酷,他們在 .renderToString(element) 方法中提供了一個 checksum 機制。前后端同構(gòu)就是保證前端和后端的dom結(jié)構(gòu)一致周瞎,不會發(fā)生重復渲染苗缩。
什么叫 首屏渲染?
簡單的說就是 vue 在瀏覽器內(nèi)存中第一次生成的虛擬 dom 樹声诸。切記是虛擬 dom 酱讶,而不是瀏覽器的dom。
了解 vue 的應該知道彼乌,所有 vue組件都有一個 render() 方法(如果使用function方式編寫的組件會把function里的所有代碼都塞到 render() 方法中去)泻肯。當 render( element, container, [callback] )方法執(zhí)行時,會執(zhí)行以下步驟:
1. 所有組件的會先進行初始化(es6執(zhí)行構(gòu)造函數(shù))慰照。
2. 所有組件的 render () 方法會被調(diào)用一次灶挟,完成這個過程后會得到一顆虛擬的 dom 樹。
3. vue 會將虛擬dom轉(zhuǎn)換成瀏覽器dom毒租,完成后調(diào)用組件的 componentDidMount() 方法告訴你已經(jīng)裝載到瀏覽器上了稚铣。
在上面這個過程成中,步驟2完成后即為完成 vue 的首屏渲染。結(jié)合 checksum 機制步驟3有可能不會執(zhí)行榛泛。
當組件狀態(tài)發(fā)生變更時( setState() 生命周期函數(shù)被調(diào)用)或者 父組件渲染時(父組件的 render() 方法被調(diào)用),當前組件的 render() 方法都會被執(zhí)行噩斟,都有可能會導致虛擬dom變更曹锨,但是這些變更和首屏渲染沒任何關(guān)系了。
在我們的項目中剃允,查看是如何渲染的
1沛简、在我們的首頁中,首次加載斥废,在 network 中椒楣,查看我們都加載了那些文件
這些文件咱們在文章頂部都講到了,這里說下 初始頁面牡肉,它是直接將 html 返回給我們的前端渲染捧灰,這個很好理解
2、點擊到詳情頁
我們發(fā)現(xiàn)這個我們的網(wǎng)絡請求统锤,并沒有繼續(xù)打包 build 走服務端渲染毛俏,而是僅僅請求了一個接口,返回了 json 數(shù)據(jù)饲窿,從這里大家應該就能看的處理煌寇,這就是所謂的雙端渲染模式。
三逾雄、總結(jié)
好啦阀溶,今天就暫時說到這里了,通過詳情頁的添加鸦泳,大家會切身體會到 nuxt 的渲染模式银锻,是如何在服務端和客戶端之間來回切換渲染的,這三篇文章大家要多看看做鹰,才能了解其中的內(nèi)涵徒仓,加油鴨~~