這是我參與更文挑戰的第 16 天,活動詳情查看: 更文挑戰前端
Lynne,一個能哭愛笑永遠少女心的前端開發工程師。身處互聯網浪潮之中,熱愛生活與技術。vue
前一陣遇到了一個Bug.....node
Error: [nuxt] Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
This node type does not support this method. at some-file.js:1
複製代碼
整整排查了2個多小時,若是要追究其根本緣由,找到了這篇文章,解釋完美。web
原文地址:Vue激活失敗(blog.lichter.io/posts/vue-h…瀏覽器
服務端渲染有不少好處,特別是當像Nuxt.js或GridSome這樣的網站,不管是使用動態SSR仍是生成靜態網站,開發 Vue-SSR 應用程序都是一件垂手可得的事。但從另外一方面來說,服務端渲染也會帶來從未見過的複雜性和錯誤。儘管大多數錯誤都被記錄在案且提供了變通的解決方案,但一個錯誤仍讓不少人困惑:Vue激活失敗。服務器
激活是當Vue轉換服務端渲染標記並使之反應的過程,所以使它能反映 Vue 的變化。 若是Vue期待與渲染的HTML不一樣的標記,就會發生激活失敗。markdown
在Vue-SSR中是這樣解釋的:Vue 在瀏覽器端接管由服務端發送的靜態HTML標記,並使其變爲由Vue管理的動態DOM的過程。app
理解:服務端已經渲染好了HTML,無需將其丟棄再從新建立全部的 DOM元素,而是去激活這些渲染好的靜態HTML,使他們成爲動態的以可以響應後續的數據變化。async
SSR + 客戶端混合 -- 瀏覽器會更改一些特殊的HTML結構,致使與Vue生成的虛擬DOM結構不匹配。ide
咱們如今意識到激活是什麼和在何時會失敗,可是咱們做爲開發者如何發現激活沒有如預期通常工做呢?有兩條錯誤消息確定會指出激活失敗但都有限制條件。
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.
複製代碼
Error: [nuxt] Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
This node type does not support this method. at some-file.js:1
複製代碼
衆所周知,激活僅在頁面首先由服務端渲染時發生,所以一般僅在你應用的初始化請求中。
(asyncData 及 data中---客戶端的data會和asyncData中的data混合)
由於當經過一個標籤導航時激活失敗是不可見的而僅在硬重載時纔可見,這使得發現激活失敗問題變得更加困難。
所以激活錯誤有時僅在分級系統或者更糟,僅在生產環境下被發現。在極少數狀況下,甚至不會打印出錯誤而僅僅時某些組件中止工做。
如今咱們瞭解瞭如何發現激活錯誤,咱們將研究致使Vue激活錯誤的典型緣由。固然咱們不可能覆蓋全部可能的緣由,由於它們差異很大,並且主要取決於你的代碼。
在如下章節中,每次提到服務端渲染,它就與兩種狀況都相關(動態SSR和靜態站點生成),由於從技術上講,二者都具備服務端渲染內容。 (除非另有聲明)
當激活失敗發生時不合理的HTML是你應該檢查的第一個地方,這也應該是有錯誤信息提示的:
This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>
複製代碼
不幸的是,不合理的HTML一般不是激活失敗的緣由。不過,你應該仔細檢查你的標記。另外,你還要確保檢查縮小設置,由於過分的HTML縮小可能會致使無效的HTML。若是您有用戶生成的輸出或來自CMS的內容,則值得驗證此內容也是有效的HTML。最後,第三方插件或服務也可能影響和操縱HTML。後者的一個常見示例是Cloudflare,當您啓用了它們的服務。如HTML縮小,Rocket loader或其餘更改頁面內容的功能時。
我建立了一個簡單的示例codeandbox,其中包含無效的HTML並觸發了激活失敗。
關於腳本:若是你向你的Vue應用中插入第三方JS文件,也能夠在Vue接收並激活來自服務器的HTML以前更改HTML
服務器和客戶端上狀態不一致是發生激活失敗最多見的緣由。像往常同樣,不一致的緣由千差萬別。
當您的網站包含日期或者時間戳時,應儘量當心並使其儘量靜態,尤爲在您的網站是靜態生成的狀況下。若是客戶端評估像new Date() 這樣的表達式,則該表達式可能會與在你服務器上開發階段檢索相同日期時生成的日期不一樣。這也讓我對公司的「關於」頁面感到困惑,在該頁面上,我想根據當前分鐘對顯示的人員進行排序。
export const deterministicRotate = (arr) => {
if (arr.length <= 1) {
return arr
}
const rotations = (new Date()).getMinutes() % arr.length
return rotations ? arr : arr.reverse()
}
複製代碼
若是用戶打開頁面的時間很奇怪,則計劃將陣列反轉。當使用動態SSR時效果很好。但當切換到靜態生成的JAMstack站點時,該功能就會成爲一個Bug。你能夠在一分鐘後點擊連接刷新,會發現名字和人正確的交換了,但圖片和原來一致。糟糕!這是因爲服務器和客戶端時間不匹配致使的。在移除不肯定型洗牌代碼後工做恢復正常。
不一致的另外一個常見緣由是用戶身份驗證。這適用於動態SSR和靜態站點生成。
當僅在客戶端(例如,在localStorage中)上存儲身份驗證狀態時,服務器「不知道身份驗證」。這將不可避免地致使激活問題,由於登陸時服務器和客戶端信息根本不一樣。所以,若是服務器不知道正在靜態生成您的頁面的身份驗證狀態,則不該在服務器端呈現任何與身份驗證相關的組件。
您可能想知道爲何它老是適用於靜態網站:由於當您生成網站時,它是HTML,而序列化的代碼是「無狀態的」。在構建階段,咱們沒法考慮「已登陸的用戶狀態」。這意味着您必須從服務器上的渲染中排除全部與身份驗證相關的組件。
除了這兩種狀況外,還有更多邊緣狀況可能會打擊您並引發不一致。即便未在此處列出,咱們也將解決激活錯誤!首先,咱們將其範圍縮小到致使問題的DOM元素。
咱們可使用您最喜好的瀏覽器上的devTools縮小問題到一個特定的組件或者DOM元素。
1)確保你在開發環境下
2)打開開發調試工具
3)觸發激活警告(一般經過重載網頁)
4)展開 [Vue Warn] The client side ... 錯誤消息查看追蹤堆棧(取決於瀏覽器,也打開彈出的VueJS列表)
5)點擊一個激活回調,將會打開Vue激活函數的源代碼
6)如今,不管什麼時候這個函數返回false都設置一個debugger,在撰寫文本時,這種狀況發生了三遍:
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
}
複製代碼
這一樣容許在激活失敗前檢查激活函數的參數。
7)最後但一樣重要的是,讓激活錯誤復現,一般再次重載頁面是有可能的但有時更困難。
8)你如今看到觸發了咱們的一個斷點,腳本中止執行了
9)如今打開調試工具的控制檯,在激活失敗的地方寫一個元素去獲取DOM元素。使用DOM元素,你將可以將激活錯誤追溯到你的Vue組件之一
10)繼續執行下一步
PS:這是用戶budden73對此StackOverflow答案的改編工做流。
如今你發現了致使問題的代碼,你首先要作的事確保你的標記(也許來自一個API)是合理的。像這樣的代碼
Text
無效,由於一個p元素不容許在其中包含其餘塊元素(如段落標籤)。可是注意,標記不容許像
這樣的標籤做爲子元素。這些標記是Vue過渡的默認標記。你能夠經過進行改變。
在debugger期間,你可以從服務器看到結果和從新繪製客戶端側。若是存在不一樣,你能夠看一看,你如何獲取數據和你在服務端或客戶端渲染了什麼。一個常見的問題是靜態網頁的認證。由於HTML在構建時生成的是無狀態的,所以不知道任何受權狀態,你應用的全部和受權有關的部分都應該旨在客戶端從新渲染。不然,在客戶端有受權狀態的用戶,由於登陸而指望從服務端獲取不一樣的HTML。而後只剩下一個選項...
最後一個解決激活錯誤的選擇是徹底避免組件出現激活錯誤。這對於在靜態生成的頁面上與身份驗證相關的組件來講是必須的,有時對於交付您不能更改但必須嵌入的內容的組件(例如,來自第三方應用程序)也是必需的。
正如咱們在一開始瞭解到的,激活僅發生在組件被同時渲染在服務端和客戶端時。爲了不激活失敗,咱們經過標籤避免從新渲染服務端組件。 惟一的缺點:該組件不包含在服務器返回的HTML中,對SEO沒有幫助。
讓咱們結束吧,如今你瞭解更多:
什麼是激活以及它作了什麼?
激活怎麼失敗的以及如何發現激活失敗?
激活失敗的通常緣由
如何調試激活失敗及解決你的應用程序
我但願這篇文章頗有見識,而且您學到了一兩件事。您是否遇到了此處未描述的激活錯誤緣由,或者我錯過了一個常見緣由?隨時在Twitter上或經過郵件給我發消息。 並且像往常同樣-若是您能宣傳並與同事分享博客文章,我將很高興。
看完這篇文章收穫很大,但願你也是~~~