当前位置: 首页 > 科技观察

说说Vue水合失败(Vuehydrationfails)

时间:2023-03-21 10:22:31 科技观察

知乎Vue-SSR激活失败SSR服务端渲染的概念,稍有经验的开发者应该不会陌生。官方文档VueSSR指南关于什么是服务端渲染,为什么使用服务端渲染,什么时候使用服务端渲染已经说的很清楚了,结合一个经典的构建过程总结了SSR的基础知识。一、基础知识什么是服务端渲染?客户端渲染是在浏览器中输出Vue组件,生成DOM并操作DOM,渲染成HTML页面展示;服务端渲染是将与服务端HTML字符串相同的组件渲染出来,直接发送给浏览器显示;VueSSR将这些服务器端渲染的静态HTML“激活”成客户端完全交互的HTML页面,服务器端和客户端渲染的HTML是混合的,体现了VueSSR的一大特点——同构。为什么要使用SSR?首屏渲染速度快;SEO,服务器端渲染页面的内容可以被搜索引擎爬虫获取;SSR在什么场景下使用?SSR服务端渲染的优势主要在于首屏渲染和SEO,那么为什么不直接全面普及呢?主要考虑以下缺点:代码复杂度增加——为了实现上述同构,应用代码需要同时兼容服务器端和客户端的运行条件,所以原来只支持运行在浏览器环境的API方法必须添加特殊处理才能在服务器呈现的应用程序中运行;构建设置和部署涉及更多要求——完全静态的单页应用程序(SPA)可以部署在任何静态文件服务器上,而服务器呈现的应用程序需要在Node.js服务器运行环境中;更大的服务器负载——一个完整的应用是在Node.js中渲染的,所以是否可以使用服务器端渲染SSR,开发者需要考虑投入产出比,如果应用系统的大部分页面都是SEO则不需要,并且首屏时间基本可以满足需求,不用SSR。结合上面第2点,SSR的使用场景:对首屏渲染时间要求高,服务器端尽量只渲染首屏内容;对SEO要求高的内容。2.主要痛点服务器端渲染有很多好处,尤其是像Nuxt.js或GridSome这样的站点,无论是使用动态SSR还是生成静态站点,开发Vue-SSR应用都是轻而易举的事。但另一方面,由于同构带来的代码复杂性和节点侧的未知错误,降低了系统应用的稳定性和可靠性,不建议在非必要场景下使用SSR开发。虽然在官方指南的指导下以及前辈们提供的灵活解决方案下,大部分错误都可以避免或解决,但还是有一些未知错误导致的错误让我们无从下手。比如我曾经遇到过SSR服务端渲染的bug。经过2个小时的排查,确定是客户端渲染失败,但是导致客户端渲染失败的原因有很多。错误:安装应用程序时出错:HierarchyRequestError:无法在“节点”上执行“appendChild”:此节点类型不支持此方法。atsome-file.js:1在SSR应用中不断踩坑的经历让我意识到:遇到的麻烦的错误,大部分都是因为Vue客户端激活失败导致的。3.什么是Vue客户端水化?客户端水化官方指南对Vue客户端水化有明确的定义:所谓客户端水化是指Vue接管浏览器端。将服务器发送的静态HTML转化为Vue管理的动态DOM的过程。这里hydration可以翻译为“注入”,可以理解为将客户端生成的虚拟DOM结构注入到服务端渲染的HTML中,让这些静态的HTML动态化。从上面的构建过程图我们也可以看出,这里的激活发生在客户端构建包到渲染成HTML的过程中。客户端激活失败。服务器已经呈现HTML。与其丢弃它并重新创建所有DOM元素,不如激活这些呈现的静态HTML并使它们动态响应后续的数据变化。但是,如果Vue客户端生成的虚拟DOM树与服务端渲染的DOM结构不匹配,就会出现客户端激活失败的情况。分为开发模式和生产模式两种情况:在开发模式下,如果无法匹配,则会退出混合模式,丢弃已有的DOM,从头开始渲染;在生产模式下,将跳过此检测,以避免性能损失。这也解释了为什么有些bug只出现在生产模式下。在排查错误的过程中,我阅读了(谷歌)一篇关于SSR服务中客户端激活失败的更详细的文章,列出了大多数可能的原因和解决方案。原文地址-Vue水化失败怎么办2.正文-Vue水化失败怎么办1.什么是Vue激活失败第一次听到“水化”这个词的时候,对我来说很抽象,并没有想出它的意思。最终,我意识到它并不像一开始听起来那么复杂:hydration是Vue转换服务器端渲染标记并使其动态响应的过程,因此它反映了Vue的动态变化。如果Vue期望与服务器呈现的HTML不同的标记,hydration将失败(又名“Vuethrowshydrationaway”)。你可以在官方VueSSR文档中阅读更多相关信息。2.如何识别激活失败我们现在知道激活是什么以及什么时候失败了,但是我们作为开发人员如何发现激活没有按预期工作?有两条错误消息肯定会指向激活失败,但都是限制因素。1)第一项只出现在开发环境中:Parent:Parent:client-hook-3.js:1:16358MismatchingchildNodesvs.VNodes:NodeList(3)[p,p,p]Array[{...}][Vuewarn]:客户端呈现的虚拟DOM树与服务器呈现的内容不匹配。这可能是由不正确的HTML标记引起的,例如在

内嵌套块级元素,或缺少。Bailinghydration并执行完整的客户端渲染。2)第二条错误消息仅在生产环境中使用静态生成的站点时出现:错误:安装应用程序时出错:HierarchyRequestError:无法在“节点”上执行“appendChild”:这节点类型不支持此方法。atsome-file.js:1众所周知,激活仅在页面首次被服务器呈现时发生,因此通常仅在您的应用程序的初始化请求中发生。通过选项卡导航时激活失败是不可见的,只能在硬重新加载时重现,这使得在开发环境中更难发现激活失败。因此,激活错误有时只在试运行系统中发现,或者更糟的是,只在生产中发现。在极少数情况下,甚至不会打印错误,但某些组件只是停止工作。3.常见的错误原因现在我们了解了如何发现激活错误,让我们检查一下Vue激活错误的典型原因。当然,这里不可能涵盖所有可能的原因,因为这些错误的原因千差万别,而且大多与代码有关。在以下部分中,每当提到服务器端渲染时,它都与两种情况(动态SSR和静态站点生成)相关,因为从技术上讲,两者都有服务器端渲染的内容(除非另有说明)。1)HTML不合理当激活失败时,首先要检查HTML是否合理。官方指南有这种坑提示,一般会有错误信息提示:这很可能是HTML标记不正确造成的,例如在

中嵌套了块级元素,或者缺少不幸的是——构思HTML通常不是激活失败的原因。不过,您应该仔细检查您的标记。此外,请务必检查您的缩小设置,因为过多的HTML缩小会导致无效的HTML。如果您有用户生成的输出或来自CMS的内容,则需要验证该内容是否也是有效的HTML。最后,第三方插件或服务也可能影响和操纵HTML。后者的一个常见示例是Cloudflare,当您启用他们的服务时——例如HTML缩小、Rocket加载程序或其他更改页面内容的功能。这是一个包含无效HTML并触发激活失败的简单示例代码和框。2)修改HTML的脚本关于脚本:如果你在你的Vue应用程序中插入第三方JS文件,你也可以在Vue从服务器接收并激活HTML之前更改HTML。3)服务器和客户端的状态不同。激活失败最常见的原因是服务端和客户端状态不一致。与往常一样,不一致的原因千差万别。日期、时间戳和随机化当网站包含日期或时间戳时,应注意使它们尽可能静态,尤其是如果网站是静态生成的。如果客户端评估像newDate()这样的表达式,则该表达式可能会产生与服务器开发期间检索到的相同日期不同的日期。这也让我对公司的“关于”页面感到困惑,我想在该页面上根据当前分钟对显示的人员进行排序。exportconstdeterministicRotate=(arr)=>{if(arr.length<=1){returnarr}constrotations=(newDate()).getMinutes()%安排。长度返回旋转?啊:啊。reverse()}如果用户在奇怪的时间打开页面,计划反转数组。使用动态SSR时效果很好。但是当切换到静态生成的JAMstack站点时,该功能就变成了一个错误。一分钟后你可以点击链接刷新,你会发现名字和人物正确对换了,但是图片和原来的一样。哎呀!这是由服务器和客户端时间不匹配引起的。删除非确定性代码后工作恢复正常。不一致授权的另一个常见原因是用户身份验证。这适用于动态SSR和静态站点生成。当身份验证状态仅存储在客户端(例如在localStorage中)时,服务器“不知道身份验证”。这将不可避免地导致激活问题,因为登录时服务器和客户端信息根本不同。因此,如果服务器不知道静态生成的页面的身份验证状态,则不应在其上呈现任何身份验证相关的组件。服务器端。你可能想知道为什么它总是适用于静态网站:因为当网站生成时,它是HTML,序列化代码是“无状态的”。我们无法在构建阶段考虑“登录用户状态”。这意味着所有与身份验证相关的组件都必须排除在服务器上的呈现之外。其他原因除了这两种情况之外,还有更多边缘情况可能会影响您并导致不一致。即使此处未列出,我们也会修复激活错误!首先,我们应该将问题缩小到导致问题的DOM元素。4.排除激活失败1)找到导致激活失败的元素我们可以使用您喜欢的浏览器上的devTools将问题缩小到特定组件或DOM元素。确保在开发环境中打开开发调试工具触发激活警告(一般通过重新加载页面)展开【VueWarn】客户端...错误信息查看tracestack(视浏览器而定,也开启弹出的VueJS列表)单击将打开Vue激活函数源代码的激活回调现在,只要此函数返回false,就会设置调试器,在写入文本时会发生三次:if(process.env.NODE_ENV!=='production'){if(!assertNodeMatch(elm,vnode,inVPre)){returnfalse//HERE}}if(process.env.NODE_ENV!=='production'&&typeofconsole!=='undefined'&&!hydrationBailed){hydrationBailed=true;console.warn('父级:',elm);console.warn('serverinnerHTML:',i);console.warn('客户端innerHTML:',elm.innerHTML);}returnfalse//HERE}if(process.env.NODE_ENV!=='production'&&typeofconsole!=='undefined'&&!hydrationBailed){hydrationBailed=true;console.warn('父级:',elm);console.warn('不匹配的childNodes与VNodes:',elm.childNodes,children);}returnfalse//HERE}这也允许在激活失败之前检查激活函数的参数。最后但并非最不重要的一点是,要重现激活错误,通常可以重新加载页面,但有时会更困难你现在看到我们的断点之一被触发并且脚本停止执行现在打开调试器的控制台,写一个元素激活无法获取DOM元素的地方。使用DOM元素,您将能够将激活错误追溯到您的Vue组件之一以继续下一步PS:这是用户budden73对这个StackOverflow答案的改编。2)确保您的HTML标记是正确的既然您已经找到导致问题的代码,您需要做的第一件事就是确保您的标记(可能来自API)是正确的。像

Text

这样的代码是无效的,因为p元素不允许在其中包含其他块元素(例如段落标签)。但请注意,标记不允许将这样的标记作为子标记,这些是Vue转换的默认标记。您可以使用进行更改。因为p元素不允许在其内部包含其他块元素(例如段落标签)。但请注意,标记不允许像3)在调试器期间解决服务器和客户端之间的不一致,您可以从服务器看到结果并重新绘制客户端。如果有差异,您可以查看如何获取数据以及在服务器或客户端上呈现的内容。一个常见的问题是静态网页的身份验证,因为HTML在构建时是无状态生成的,因此不知道任何授权状态,应用程序的所有授权相关部分都应该在客户端重新呈现。否则,在客户端具有授权状态的用户因为已登录而期望来自服务器的不同HTML。那么只剩下一个解决方案......4)FinalAvoidance:解决激活错误的最后一个选项是避免来自组件。这对于静态生成的页面上的身份验证相关组件是必需的,有时对于提供您无法更改但必须嵌入的内容(例如,来自第三方应用程序)的组件也是必需的。正如我们在开始时所了解的,仅当组件在服务器和客户端上都呈现时才会激活。为了避免激活失败,可以通过标签避免重新渲染服务端组件。唯一的缺点:该组件不包含在服务器返回的HTML中,对SEO没有帮助。5.总结本文到此结束!现在你更了解Vue激活失败:什么是激活,它有什么作用?激活失败怎么办,如何发现激活失败?激活失败的常见原因如何调试激活失败并解决您的应用程序我希望这篇文章很有见地并且您学到了一两件事。您遇到的是此处未描述的激活错误原因,还是我遗漏的常见原因?请随时在Twitter上或通过邮件给我发消息。和往常一样-如果您愿意传播信息并与您的同事分享博客文章,我将非常高兴。三、踩坑总结这篇博客讲了我在服务端渲染中遇到的大部分客户端激活失败和报错问题,甚至包括了我没有遇到的问题。当然,这个博客也有缺点。比如这是2020年的一篇文章,其中一些问题可能会有新的进展。当然可能还有很多和我一样还在踩坑的新手。最后,整理一下自己在开发期间踩坑的经验:1.同构因为我们使用同构的目的是写一个尽可能通用的代码,让它在两端运行。因此,我们需要熟悉不同终端的运行环境,至少要熟悉相关的API。Node.js终端上没有浏览器对象,所以无法进行window、document、DOM操作。同样,浏览器端也没有进程对象,它们各自的API实现也不同,需要特别注意。还有一个比较麻烦的就是第三方库的引入。有时候你不知道导入的库是否能在Node端/浏览器端完全运行。如果只能在纯浏览器环境下运行,可以在createdstage之后导入执行,避免在Node.js下执行。2.数据预取问题:使用v-html注入动态获取的HTML内容时。如果HTML内容在在浏览器中完全渲染完毕,其内容正常执行,事件绑定也是正常的绑定到当时的DOM元素上。第二次渲染,使用CSR:此时HTML以v-html的形式进行渲染和替换,但是v-html本质上是一个innerHTML操作,所以内容,无论第一次还是第二次渲染,只把内容交给v-html页面,然后单独在生命周期更新(页面有HTML内容渲染完成),创建是否存在,如果是则先删除再添加,避免添加多余的