场景

比如做了一定程度的首屏优化, 有一个已经渲染了 App Shell 的 HTML,
同时, 这个页面要等待 js 加载完才能运行, 所以设计了 CSS loading 动画,
还有一个因素, 浏览器端渲染的 HTML 与静态页面的 HTML 存在不一致.

也就是说当页面被加载, 服务端渲染的 HTML 中的 CSS3 动画, 前端 js 初始化,
整个过程 CSS3 的动画需要联系不能被破坏.

问题

如果用 jQuery 搭配从前的服务端模板引擎渲染, 其实没多大问题.
但是到了 React 这种 Virtual DOM 方案当中, 就存在 bug.
首先 React 使用通过 Virtual DOM Diff 计算出更新操作,
然后才在 DOM 上做真实的操作, 加上 Model, 对应过程是这样:

React 生成的首屏, 到了前端再初始化 React 时, 就可能存在问题,
React 对于已经存在的 HTML 会通过 checksum 属性来匹配,
判断已存在的 HTML 与新的一致, 直接用已有的 DOM, 否则要重新生成 HTML,
由于很容易出现不一致, 所以 HTML 替换是容易出现的:

按照 React 目前的处理, 整个 DOM 会被替换掉, CSS3 动画就会实现跳跃,
除非框架层面对 DOM 做遍历检查, 否则没法直接修复问题.
当然绕过问题的办法也许有, 比如改变动画方案, 或者严格保持 HTML 一致.

Respo 的方案

我在 Respo 里加了一个方案, 大致的思路是 Mock 一份 Virtual DOM,
把首屏也转换成一个 DOM Patch 的操作, 从而保证 DOM 本身不被销毁掉:

伪造 Virtual DOM 需要框架支持, 所以在 React 里不好做,
当然不妨碍我在 Respo 里直接加上这个功能, 自动伪造一个状态,
比如原来直接渲染的代码是这样的写的:

(defn -main! []
  (render-app!)
  (add-watch store-ref :changes render-app!)
  (add-watch states-ref :changes render-app!))

为了伪造 Virtual DOM, 就要在 render 之前检测, 然后调用 API,
render-element 的表达式是用来生成 Virtual DOM 的, 先忽略细节,
然后 falsify-stage! 这个 API 拿到会对内部状态进行处理:

(defn -main! []
  (falsify-stage!
    (.querySelector js/document "#app")
    (render-element (comp-container @store-ref ssr-stages) global-states)
    dispatch!)
    
  (render-app!)
  (add-watch store-ref :changes render-app!)
  (add-watch states-ref :changes render-app!))

具体的步骤是往全局缓存的 Virtual DOM 对象上做一次赋值,
注意 mutate-element 是去掉事件, 因为纯 HTML 不好处理绑定事件:

(defn falsify-stage! [target element dispatch!]
  (reset! global-element (mute-element element))
  (reset! cache-element element)
  (let [deliver-event (build-deliver-event global-element dispatch!)]
    (initialize-instance target deliver-event)))

然后等到后面再调用 render-app! 时, 就会进行一次检测,
如果已经存在, 就自动进入 Diff/Patch 的流程了, 而不会替换掉原有的:

(defn render! [markup target dispatch states-ref]
  (if (some? @global-element)
    (rerender-app markup target dispatch states-ref)
    (mount-app markup target dispatch states-ref)))

演示

早先已经有一个 Demo 验证而 API 的正确性了, 可以查看:
https://github.com/Respo/ssr-...
今天加了一个 Demo, 专门实现了动画, 以验证具体的效果:
https://github.com/Respo/firs...
这个页面模拟了这样一个过程:

  • 显示渲染好的 HTML

  • 等待 1s

  • 缩小方块, 提示开始抓数据

  • 等待 1s

  • 加载完成, 改变整个页面样式

其中旋转的方块是一直处于动画状态, 从 HTML 加载好, 到 js 渲染完成,
中间会因为 js 加载完成而产生一次 DOM Patch 的 transition 动画,
而原来的 CSS animation 不受到影响. 所以验证是可行的.


题叶
17.3k 声望2.6k 粉丝

Calcit 语言作者