-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
从一次 vue ssr 渲染客户端报错, 来看 ssr 客户端激活过程 #31
Labels
Comments
你好,我尝试了第二种做法,但是并没有起作用,请问第一种方法,如何保证客户端跟服务端初次渲染时内容一致呢? |
我做了n种测试,比如v-if都改成v-show,data里数据都删掉,created和mounted中不做任何修改,都不行,经测试,只要template里出现三层及以上嵌套就会报错。但我不知道为什么。加 |
最终我解决了,是在其他地方看到的别人的做法,这里引用一下。nuxt/nuxt#5800 (comment) |
@redteaLiufang 能不能发一下有问题的代码, 我调试看看 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
首先回顾下问题 vuejs/vue#10937
问题发生在这一块
vue 判断 vnode 的 class, 然后去和 dom 的 class 对比, 如果不一致就去更新.
但是由于 app.js 中的
v-if
在服务端和客户端结果不一致, 服务端时为 false, 渲染出了 comment(注释), 而客户端时为 true, 渲染出了 div.在
updateClass()
中, vnode 的 tag 是 div, 而 vnode 的 elm 却是 comment. 因为 comment 节点是没有setAttribute
方法的, 所以就报错了.为什么会这样
我们向上查找调用栈, 到
hydrate()
方法, hydrate 是在浏览器中运行的, 是根据 vnode 更新 dom 的 patch 过程我们来看这块
这里是一个递归调用, vue 逐个去对比 elm.childNodes 和 vnode.children, 并对子节点 重复 patch 过程.
我们能收获一个信息, vue 更新子节点时, 是按节点顺序去匹配的. elm.childNodes 分别是 [comment, h2], 而 vnode.children 分别是 [div, h2], 于是当 vue 对第一个子节点做 patch 时(
hydrate(comment node, div vnode)
), 发生了错误也就是说, vue 并没有检查 dom 不匹配的情况
来个有趣的实验
将 app.js 修改为
ssr 的渲染结果是
<h1 class="ssr" style="color:red;">111</h1>
而客户端激活后的结果是
<h1 class="csr" style="color: blue;">222</h1>
class / style / innerText 都更新了, 但是 tag 没变!
如果你去掉 style 话, 就只更新 innerText, class 也不变了
接下来分析下具体的 patch 过程
vue patch 过程
这块是组件触发 patch 的地方
遍历 data 上的属性, 调用
isRenderedModule(key)
判断是否需要调用invokeCreateHooks
,invokeCreateHooks
就是一系列 dom 更新操作看
isRenderedModule
的实现, 就可以解释为什么去掉 style 后, 就不更新 class 了invokeCreateHooks
的逻辑很简单, 就是以 vnode 作为参数, 执行预置的 cbs.create hookcbs.create hook 是在
createPatchFunction
方法一开始初始化的其中 backend.modules 是这么来的
每个 module 都是一些 hook 定义, 当组件执行到该生命周期时, 就会逐个执行 module 中定义的该生命周期 hook
platformModules 就是浏览器相关的操作, 我们以 style 为例
style 包含
create
和update
两个 hooks, 分别对应组件创建时和更新时, 执行的方法都是updateStyle
updateStyle
方法就是更新 dom style 的操作再看 klass module, 因为
class
是关键字, 所以这里命名为了 klass, 执行的是更新 dom class 的操作回过头来看问题
结合上述分析, 我们可以发现问题所在, 在
platformModules
中包含很多 dom 更新操作, 但是不包括 dom 的匹配和重建, 而是直接在已有的 dom 节点上更新在 app.js 中, 我们使用了 style 属性, 于是触发了 create 阶段的 dom 更新, 但是因为实际的 dom 是 comment, 并不支持更新 class 和 style 的操作, 所以报错
实际上, vue ssr 是要求在服务端和客户端渲染结果一致的, 官网中有这么一段
怎么解决问题
有两种解决方案
mounted
事件中更新,mounted
仅在客户端执行v-show
替代v-if
, v-if 会渲染为注释, 而 v-show 会渲染为 dom + display:none, dom 可以在客户端激活时正确更新未解之谜
vue 为什么不选择在 patch 的时候检查一下 dom 是否匹配呢? 不匹配时直接抛弃旧 dom 创建新的
The text was updated successfully, but these errors were encountered: