自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊一聊 Vue-SSR 激活失?。╒ue hydration fails)

開發(fā) 項目管理
服務(wù)端渲染有很多好處,特別是當像 Nuxt.js 或 GridSome 這樣的網(wǎng)站,無論是使用動態(tài) SSR 還是生成靜態(tài)網(wǎng)站,開發(fā) Vue-SSR 應(yīng)用程序都是一件輕而易舉的事。

認識 Vue-SSR 激活失敗

對于 SSR 服務(wù)端渲染這個概念稍有經(jīng)驗的開發(fā)應(yīng)該都不陌生,官方文檔 Vue SSR 指南 對于什么是服務(wù)端渲染、為什么使用服務(wù)端渲染以及什么時候使用服務(wù)端渲染已經(jīng)說的很清楚了,結(jié)合一張經(jīng)典的構(gòu)建過程總結(jié)關(guān)于 SSR 的基本知識。

1. 基本常識

什么是服務(wù)端渲染?

  • 客戶端渲染是在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操作 DOM,并渲染為 HTML 頁面展示;
  • 服務(wù)端渲染是將同一個組件渲染為服務(wù)器端的 HTML 字符串,將它們直接發(fā)送到瀏覽器展示;
  • Vue SSR 則將這些服務(wù)端渲染的靜態(tài) HTML "激活"為客戶端上完全可交互的 HTML 頁面,服務(wù)端與客戶端渲染的 HTML 混合,體現(xiàn)了 Vue SSR 的一大特性--同構(gòu)。

為什么使用 SSR?

  • 首屏渲染速度快;
  • SEO,服務(wù)端渲染的頁面內(nèi)容可以被搜索引擎爬蟲獲取;

什么場景下使用 SSR?

SSR 服務(wù)端渲染的優(yōu)勢主要在于首屏渲染與 SEO,那為什么不直接全面推廣使用呢?主要考慮到存在以下劣勢:

  • 代碼復雜度增加 - 為了實現(xiàn)前面提到的同構(gòu),應(yīng)用代碼中需要兼容服務(wù)端和客戶端兩種運行情況,那么原先只支持瀏覽器環(huán)境運行的 API 方法必須增加特殊處理才能在服務(wù)端渲染程序中運行;
  • 涉及構(gòu)建設(shè)置和部署有更多要求 - 完全靜態(tài)單頁面應(yīng)用程序 (SPA) 可以部署在任何靜態(tài)文件服務(wù)器上,而服務(wù)器渲染應(yīng)用程序,需要處于 Node.js server 運行環(huán)境;
  • 更多的服務(wù)器負載 - 在 Node.js 中渲染完整的應(yīng)用程序,

所以是否可以使用服務(wù)端渲染 SSR,需要開發(fā)者考慮投入產(chǎn)出比,如果應(yīng)用系統(tǒng)的大多數(shù)頁面都不需要 SEO,且首屏時間基本可以滿足需求,使用 SSR 就沒有必要了。結(jié)合上面的第 2 點, SSR 的使用場景:

  • 對首屏渲染時間要求高,且盡可能只把首屏內(nèi)容放到服務(wù)端渲染;
  • 對 SEO 要求高的內(nèi)容。

2. 主要痛點問題

服務(wù)端渲染有很多好處,特別是當像 Nuxt.js 或 GridSome 這樣的網(wǎng)站,無論是使用動態(tài) SSR 還是生成靜態(tài)網(wǎng)站,開發(fā) Vue-SSR 應(yīng)用程序都是一件輕而易舉的事。但從另一方面來講,由于同構(gòu)帶來的代碼復雜性與 node 端未知錯誤,降低了系統(tǒng)應(yīng)用的穩(wěn)定性與可靠性,并不推薦在非必要場景下使用 SSR 開發(fā)。盡管在官方指南的指導下,以及在前人踩坑后提供了變通的解決方案,多數(shù)錯誤都可以避開或者解決,但仍有未知錯誤導致的報錯致使我們無從下手。

比如我曾在 SSR 服務(wù)端渲染中遇到了一個 Bug,排查了整整 2 個小時,定位為客戶端渲染失敗,但導致客戶端渲染失敗的原因又有很多。

Error: Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
This node type does not support this method. at some-file.js:1

在 SSR 應(yīng)用中不斷踩坑地經(jīng)歷讓我意識到:大多數(shù)時候遇到的棘手錯誤都是 Vue 客戶端激活失敗導致的。

3. 什么是 Vue 客戶端激活失敗(client-side hydration)客戶端激活

官方指南對 Vue 客戶端激活(client-side hydration)定義很清晰:

所謂客戶端激活,指的是 Vue 在瀏覽器端接管由服務(wù)端發(fā)送的靜態(tài) HTML,使其變?yōu)橛?Vue 管理的動態(tài) DOM 的過程。

這里 hydration,又可以翻譯為“注入”,可以了理解為將在客戶端生成的虛擬 DOM 結(jié)構(gòu)注入到服務(wù)端渲染出的 HTML 中使這些靜態(tài)的 HTML 變?yōu)閯討B(tài)的。從上面的構(gòu)建過程圖我們也可以看到,這里的激活發(fā)生在客戶端構(gòu)建完成的包到渲染為 HTML 的過程中。

客戶端激活失敗

服務(wù)端已經(jīng)渲染好了 HTML,無需將其丟棄再重新創(chuàng)建所有的 DOM 元素,而是去激活這些渲染好的靜態(tài) HTML,使他們成為動態(tài)的以能夠響應(yīng)后續(xù)的數(shù)據(jù)變化。但如果 Vue 客戶端生成的虛擬 DOM 樹與服務(wù)端渲染的 DOM 結(jié)構(gòu)無法匹配,就會發(fā)生客戶端激活失敗。

這里又分為開發(fā)模式和生產(chǎn)模式兩種情況:

在開發(fā)模式下,如果無法匹配,它將退出混合模式,丟棄現(xiàn)有的 DOM 并從頭開始渲染;

在生產(chǎn)模式下,此檢測會被跳過,以避免性能損耗。

這也就能說明為什么有些錯誤僅在生產(chǎn)模式下才會出現(xiàn)。

在排查錯誤問題的過程中,讀到(谷歌)了一篇關(guān)于 SSR 服務(wù)中客戶端激活失敗梳理較為細致的文章,列舉出了大多數(shù)可能出現(xiàn)的原因以及解決方案。

原文地址 - What to do when Vue hydration fails

二、正文 - What to do when Vue hydration fails

1. 什么是 Vue 激活失敗

第一次聽到"hydration"這個詞時,它對我來說非常抽象,我想不出它的意思。最終,我意識到它并不像一開始聽起來那么復雜:

hydration 是 Vue 轉(zhuǎn)換服務(wù)器端渲染的標記并使其具有動態(tài)反應(yīng)性的過程,因此它可以反映來自 Vue 的動態(tài)更改。

如果 Vue 期望與服務(wù)端渲染的 HTML 不同的標記,則 hydration 將失敗(也稱為“Vue 將 hydration 丟棄”)。你可以在官方的 Vue SSR 文檔中閱讀更多相關(guān)內(nèi)容。

2. 如何識別激活失敗

我們現(xiàn)在已經(jīng)意識到激活是什么和在什么時候會失敗,但是我們作為開發(fā)者要如何發(fā)現(xiàn)激活沒有如預期一般工作呢?

有兩條錯誤消息肯定會指出激活失敗但都有限制條件。

1)第一條是僅在開發(fā)環(huán)境中出現(xiàn):Parent:

Parent:  <div class="container"> client-hook-3.js:1:16358
Mismatching childNodes vs. VNodes: NodeList(3) [ p, p, p ] Array [ {} ]

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content.
This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>.
Bailing hydration and performing full client-side render.

2)第二條錯誤消息是僅在生產(chǎn)環(huán)境下使用靜態(tài)生成站點時:

Error: Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
This node type does not support this method. at some-file.js:1

眾所周知,激活僅在頁面首先由服務(wù)端渲染時發(fā)生,因此通常僅在你應(yīng)用的初始化請求中。

當通過一個標簽導航時激活失敗是不可見的,僅能在硬重載時才能復現(xiàn),這就使得在開發(fā)環(huán)境下發(fā)現(xiàn)激活失敗問題變得更加困難。

因此激活錯誤有時僅在階段系統(tǒng)或者更糟,僅在生產(chǎn)環(huán)境下被發(fā)現(xiàn)。在極少數(shù)情況下,甚至不會打印出錯誤而僅僅時某些組件停止工作。

3. 一般引發(fā)錯誤的原因

現(xiàn)在我們了解了如何發(fā)現(xiàn)激活錯誤,接下來將研究導致 Vue 激活錯誤的典型原因。當然這里不可能覆蓋所有可能的原因,因為這些錯誤原因差別很大,而且主要取決于代碼。

在以下章節(jié)中,每次提到服務(wù)端渲染,它就與兩種情況都相關(guān)(動態(tài) SSR 和靜態(tài)站點生成),因為從技術(shù)上講,兩者都具有服務(wù)端渲染內(nèi)容(除非另有說明)。

1)不合理的 HTML當激活失敗發(fā)生時,首先要檢查的應(yīng)該是 HTML 是否合理,官方指南有這類踩坑提示,同時一般會有錯誤信息提示:


This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>

不幸的是,不合理的 HTML 通常不是激活失敗的原因。不過,你還是應(yīng)該仔細檢查你的標記。

另外,你還要確保檢查縮小設(shè)置,因為過度的 HTML 縮小可能會導致無效的 HTML。

如果你有用戶生成的輸出或來自 CMS 的內(nèi)容,需要驗證此內(nèi)容也是有效的 HTML。

最后,第三方插件或服務(wù)也可能影響和操縱 HTML。后者的一個常見示例是 Cloudflare,當你啟用了它們的服務(wù) -- 如 HTML 縮小,Rocket loader 或其他更改頁面內(nèi)容的功能時。

這里有一個簡單的示例 codeandbox,其中包含無效的 HTML 并觸發(fā)了激活失敗。

2)修改 HTML 的腳本關(guān)于腳本:如果向 Vue 應(yīng)用中插入第三方 JS 文件,也可以在 Vue 接收并激活來自服務(wù)器的 HTML 之前更改 HTML。

3)服務(wù)器和客戶端的狀態(tài)不同服務(wù)器和客戶端上狀態(tài)不一致是發(fā)生激活失敗最常見的原因。像往常一樣,不一致的原因千差萬別。

日期、時間戳和隨機化

當網(wǎng)站包含日期或者時間戳時,應(yīng)盡可能小心并使其盡可能靜態(tài),尤其在網(wǎng)站是靜態(tài)生成的情況下。如果客戶端評估像 new Date() 這樣的表達式,則該表達式可能會與在服務(wù)器上開發(fā)階段檢索相同日期時生成的日期不同。這也讓我對公司的“關(guān)于”頁面感到困惑,在該頁面上,我想根據(jù)當前分鐘對顯示的人員進行排序。

export const deterministicRotate = (arr) => {
if (arr.length <= 1) {
return arr
}
const rotations = (new Date()).getMinutes() % arr.length

return rotations ? arr : arr.reverse()
}

如果用戶打開頁面的時間很奇怪,則計劃將陣列反轉(zhuǎn)。當使用動態(tài) SSR 時效果很好。但當切換到靜態(tài)生成的 JAMstack 站點時,該功能就會成為一個 Bug。你可以在一分鐘后點擊鏈接刷新,會發(fā)現(xiàn)名字和人正確的交換了,但圖片和原來一致。糟糕!這是由于服務(wù)器和客戶端時間不匹配導致的。在移除不確定性代碼后工作恢復正常。

  • 授權(quán)

不一致的另一個常見原因是用戶身份驗證。這適用于動態(tài) SSR 和靜態(tài)站點生成。當僅在客戶端(例如,在 localStorage 中)上存儲身份驗證狀態(tài)時,服務(wù)器“不知道身份驗證”。這將不可避免地導致激活問題,因為登錄時服務(wù)器和客戶端信息根本不同。因此,如果服務(wù)器不知道正在靜態(tài)生成頁面的身份驗證狀態(tài)**,**則不應(yīng)在服務(wù)器端呈現(xiàn)任何與身份驗證相關(guān)的組件。你可能想知道為什么它總是適用于靜態(tài)網(wǎng)站:因為當生成網(wǎng)站時,它是 HTML,而序列化的代碼是“無狀態(tài)的”。在構(gòu)建階段,我們無法考慮“已登錄的用戶狀態(tài)”。這意味著必須從服務(wù)器上的渲染中排除所有與身份驗證相關(guān)的組件。

  • 其他原因

除了這兩種情況外,還有更多邊緣情況可能會打擊你并引起不一致。即使未在此處列出,我們也將解決激活錯誤!首先,我們應(yīng)該將其范圍縮小到導致問題的 DOM 元素。

4. 解決激活失敗問題

1)發(fā)現(xiàn)導致激活失敗的元素我們可以使用您最喜愛的瀏覽器上的 devTools 縮小問題到一個特定的組件或者 DOM 元素。

  • 確保你在開發(fā)環(huán)境下
  • 打開開發(fā)調(diào)試工具
  • 觸發(fā)激活警告(通常通過重載網(wǎng)頁)
  • 展開[Vue Warn] The client side ...錯誤消息查看追蹤堆棧(取決于瀏覽器,也打開彈出的 VueJS 列表)
  • 點擊一個激活回調(diào),將會打開 Vue 激活函數(shù)的源代碼
  • 現(xiàn)在,無論何時這個函數(shù)返回 false 都設(shè)置一個 debugger,在撰寫文本時,這種情況發(fā)生了三遍:
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false //HERE
}
}
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true;
console.warn('Parent: ', elm);
console.warn('server innerHTML: ', i);
console.warn('client innerHTML: ', elm.innerHTML);
}
return false //HERE
}
 if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!hydrationBailed
) {
hydrationBailed = true;
console.warn('Parent: ', elm);
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children);
}
return false //HERE
}

這同樣允許在激活失敗前檢查激活函數(shù)的參數(shù)。

  • 最后但同樣重要的是,讓激活錯誤復現(xiàn),通常再次重載頁面是有可能的但有時更困難
  • 你現(xiàn)在看到觸發(fā)了我們的一個斷點,腳本停止執(zhí)行了
  • 現(xiàn)在打開調(diào)試工具的控制臺,在激活失敗的地方寫一個元素去獲取 DOM 元素。使用 DOM 元素,你將能夠?qū)⒓せ铄e誤追溯到你的 Vue 組件之一
  • 繼續(xù)執(zhí)行下一步 PS:這是用戶 budden73對此 StackOverflow 答案改編后的問題定位順序。

2)確保你的 HTML 標記合理現(xiàn)在你發(fā)現(xiàn)了導致問題的代碼,你首先要做的事確保你的標記(也許來自一個 API)是合理的。像這樣的代碼<p><p>Text</p></p> 無效,因為一個p元素不允許在其中包含其他塊元素(如段落標簽)。但是注意,標記不允許像

這樣的標簽作為子元素,這些標記是 Vue 過渡的默認標記。你可以通過<Transition tag="div">進行改變。因為一個p元素不允許在其中包含其他塊元素(如段落標簽)。但是注意,標記不允許像

3)解決服務(wù)器與客戶端之間的不一致在 debugger 期間,你能夠從服務(wù)器看到結(jié)果和重新繪制客戶端側(cè)。如果存在不同,你可以看一看,你是如何獲取數(shù)據(jù)和你在服務(wù)端或客戶端渲染了什么。一個常見的問題是靜態(tài)網(wǎng)頁的認證,因為 HTML 在構(gòu)建時生成的是無狀態(tài)的,因此不知道任何授權(quán)狀態(tài),你應(yīng)用的所有和授權(quán)有關(guān)的部分都應(yīng)該在客戶端重新渲染。否則,在客戶端有授權(quán)狀態(tài)的用戶,因為登錄而期望從服務(wù)端獲取不同的 HTML。然后只剩下一個方案...

4)最終避免措施:最后一個解決激活錯誤的選擇是完全避免組件出現(xiàn)激活錯誤。這對于在靜態(tài)生成的頁面上與身份驗證相關(guān)的組件來說是必須的,有時對于交付你不能更改但必須嵌入的內(nèi)容的組件(例如,來自第三方應(yīng)用程序)也是必需的。正如我們在一開始了解到的,激活僅發(fā)生在組件被同時渲染在服務(wù)端和客戶端時。為了避免激活失敗,可以通過標簽避免重新渲染服務(wù)端組件。唯一的缺點:該組件不包含在服務(wù)器返回的 HTML 中,對 SEO 沒有幫助。

5. 總結(jié)

這篇文章結(jié)束了!現(xiàn)在你了解了更多關(guān)于 vue 激活失敗的知識:

  • 什么是激活以及它做了什么?
  • 激活怎么失敗的以及如何發(fā)現(xiàn)激活失敗?
  • 激活失敗的一般原因
  • 如何調(diào)試激活失敗及解決你的應(yīng)用程序

我希望這篇文章很有見解,并且你學到了一兩件事。你是否遇到了此處未描述的激活錯誤原因,或者我錯過了一個常見原因?隨時在 Twitter 上或通過郵件給我發(fā)消息。而且像往常一樣 —— 如果您能宣傳并與同事分享博客文章,我將很高興。

三、踩坑小結(jié)

這篇博客講到了大多數(shù)我在服務(wù)端渲染中遇到的客戶端激活失敗報錯問題,甚至也包括了我沒有遇到的問題。當然這篇博客也有不足,比如這是 2020 年的文章,其中一些問題也許有了新的進展,當然可能也有許多如我一般的新手還在踩坑。最后整理一下我在開發(fā)期間學習踩坑所得經(jīng)驗:

1. 同構(gòu)

因為我們采用同構(gòu)的目的是寫一份盡量通用的代碼, 讓它運行在兩端。所以我們需要熟悉不同端的運行環(huán)境, 至少要熟悉相關(guān) API,Node.js 端是沒有瀏覽器對象的, 所以 window, document, DOM 操作都沒法執(zhí)行。同理, 瀏覽器端是沒有 process 對象的,他們各自的 API 實現(xiàn)也有差別, 這點需要特別留意。還有比較麻煩的就是第三方庫的引入, 有時候你并不知道引入的庫能不能完全運行在 Node 端/瀏覽器端,如果它只能運行在純?yōu)g覽器環(huán)境, 那可以在 created 階段之后引入和執(zhí)行來避開 Node.js 下執(zhí)行。

2. 數(shù)據(jù)預取問題

問題:使用 v-html 注入動態(tài)獲取的 HTML 內(nèi)容的時候. 如果 HTML 內(nèi)容有<script>

所包含的 JS 代碼, 會發(fā)現(xiàn) script 中的事件綁定失效。

分析:其實在這里頁面被渲染了兩次, 第一次是發(fā)生在 SSR 直接交給瀏覽器的時候: 此時<script>完整被渲染在瀏覽器里, 其內(nèi)容正常執(zhí)行, 事件綁定也正常的綁定在了當時的 DOM 元素上。而第二次渲染時, 走的是 CSR: 在這時由于是以 v-html 的方式來渲染替換 HTML, 但是 v-html 實質(zhì)上是 innerHTML 操作, 這樣<script>雖然會被替換上去, 但是其中的內(nèi)容不會執(zhí)行(innerHTML 為安全性考慮而設(shè)計)。所以經(jīng)過這樣兩次渲染之后, 此時的 DOM 元素是第二次渲染時得到的, 而正常執(zhí)行過的 JS 的事件綁定是綁在在第一次渲染出來的 DOM 元素上, 這樣就出現(xiàn)了雖然 DOM 存在,但是無法觸發(fā)該 DOM 上的事件的情況。

解決方法: 將獲取到的 HTML 內(nèi)容進行匹配, 剔出<script>內(nèi)容, 無論第一次或第二次渲染, 只將內(nèi)容交給 v-html 頁面, 然后單獨在生命周期 updated(頁面已將 HTML 內(nèi)容渲染完成)中將<script>創(chuàng)建出來, 添加到頁面里自動執(zhí)行, 實現(xiàn)綁定。并在下一次頁面重渲染(可能是頁面跳轉(zhuǎn)來到)時判斷<script>是否存在, 如果真則先刪去再添加, 這樣避免添加多余的<script>塊。

** 那么頁面為什么會渲染兩次呢? **

這是由于 Vue SSR 在初始頁面渲染完成后會有一次 hydration 過程, 在這個過程中會照常執(zhí)行流程 mounted 等生命周期。此時會判定是否此時的組件渲染出的內(nèi)容與 SSR 渲染得到的內(nèi)容一致, 如果出現(xiàn)不一致就會單獨執(zhí)行一次額外的 CSR(客戶端激活失敗), 以達到頁面被能正確地渲染。而因為我們使用了 v-html, 這個過程只有在 CSR 時才會被執(zhí)行, 所以導致了兩次渲染出來的內(nèi)容不一致, 觸發(fā)了 Vue SSR 的”補償渲染機制”, 進而執(zhí)行了第二次渲染。

參考文章

  • [Vue SSR 指南] (https://ssr.vuejs.org/zh/guide/hydration.html)
  • [What to do when Vue hydration fails] (https://blog.lichter.io/posts/vue-hydration-error/)
  • [徹底理解服務(wù)端渲染 - SSR 原理 ] (https://github.com/yacan8/blog/issues/30)
  • [Vue.js 服務(wù)端渲染(SSR)不完全指北] (https://lqs469.com/Vue.js%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93(SSR)%E4%B8%8D%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8C%97/)
  • [從頭開始,徹底理解服務(wù)端渲染原理(8 千字匯總長文)] (https://juejin.cn/post/6844903881390964744#heading-16)
責任編輯:姜華 來源: 微醫(yī)大前端技術(shù)
相關(guān)推薦

2021-02-22 14:04:47

Vue框架項目

2021-02-15 15:36:20

Vue框架數(shù)組

2025-02-18 00:00:05

vue后端權(quán)限

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2018-06-07 13:17:12

契約測試單元測試API測試

2020-08-11 06:53:33

Vue前端代碼

2022-08-08 08:25:21

Javajar 文件

2021-08-04 09:32:05

Typescript 技巧Partial

2018-11-29 09:13:47

CPU中斷控制器

2019-02-13 14:15:59

Linux版本Fedora

2021-01-29 08:32:21

數(shù)據(jù)結(jié)構(gòu)數(shù)組

2021-02-06 08:34:49

函數(shù)memoize文檔

2023-05-15 08:38:58

模板方法模式

2022-11-01 08:46:20

責任鏈模式對象

2023-07-06 13:56:14

微軟Skype

2020-10-15 06:56:51

MySQL排序

2020-09-08 06:54:29

Java Gradle語言

2022-03-08 16:10:38

Redis事務(wù)機制
點贊
收藏

51CTO技術(shù)棧公眾號