前面的几篇里,我们都没有涉及需要从后台抓取数据来渲染的页面,这块可能是最复杂的部分了,本文里我们就来讨论这个问题 – Vue SSR 如何做后台数据预取。
我们面临什么样的难题?
如果一个页面需要从数据后台的 api 获取数据来渲染页面,那么从 SSR 的架构来说就要处理以下2个场景:
-
这个页面不是首屏
数据应该由前端去数据后台获取,并渲染页面。这个场景和普通的 client side Vue 项目基本一样。
-
这个页面是首屏
数据应该由后台获取,因为这个作为用户访问的首屏,是要由 server-bundle 渲染生成 index.html (壳子 HTML) 的。HTML 页面和 client-bundle 一起返回给浏览器后, 在浏览器端做 hydrate。
这问题就又来了,client-bundle 并没有后台的那份数据。数据都不一致,hydrate 根本无从谈起。怎么办呢?可能 2 个思路:
- client-bundle 再去后台拿一次。细想一下完全不可行。首先,这是对数据后台的重复开销,能避免就应该避免。其次,对同一个 api 接口的2次调用,未必能得到同样的数据(比如获取论坛最新帖子的api),数据不同的话,hydrate 还是会失败。
- 把后台的这份数据想办法返回给前台。Bingo!Vue 的 SSR 就是这么做的。(我想可能所有的 SSR 方案都得这么做)
余文中我们主要分析场景2,即这个页面是首屏的场景。这个页面不是首屏的场景相对简单,我们下一篇再说。
Vue SSR 的方案
数据在 server-bundle 和 client-bundle 之间共享,看到数据共享几个字眼,可能大家就很自然的想起来 Vuex
. Vue 的 SSR 中也确实是以 Vuex 为基础的。如大致的流程,上图所示:
- 用户通过浏览器访问 SSR 网站时,server-bundle 会根据用户的 url 导航到相应页面,并从数据后台取到这个页面所需要的数据。(数据被存储在 Vuex store 里)
- server-bundle 根据 Vuex store 里的数据对 index.html(即壳子HTML) 做渲染. 这个 index.html 的特别之处在于 Vue store 的内容会在序列化后存在其中。(上图中选中的部分)
- 页面返回给浏览器后,client-bundle 开始初始化,它会读取 index.html 中这份序列化的 Vue store,并用这个值来初始化自己的 Vuex store,进而完成客户端的 hydrate.
方案很完美,但是为了做到前后台代码同构,实现还是挺复杂的。
Vue SSR 实现
-
创建一个 Vuex store,并按老规矩使用工厂函数。
-
修改 app.js 以引入 Vuex
除了使用工厂函数外,这个和 client-only 的 Vue 项目并无区别,就不多解释啦。
-
改造需要获取异步数据的 Vue 组件
Vue SSR 的处理方式是给页面的 Vue 对象加一个静态的 asyncData 函数,专门用于去获取数据。
-
改造 server Bundle
路由 ready 之后,我们从路由对象里去除所有的组件,然后遍历所有的这些组件, 并检查组件是否有
asyncData
的静态方法,如果有,则用store
和currentRoute
调用之,等待所有的调用成功后,取出 Vuex store 里 state 对象,并赋值给函数的参数context
.state 对象里是啥? 所有的
asyncData
方法都是基于 Vuex 实现,那么这一番调用后,这个页面所有的组件需要的数据就都在 Vuex store 的 state 里了。- 改造 server.js
这个 context 经过
createApp
的处理后已经多了个state
属性. renderToString 会判断 context 是否有 state 的属性,如果有则把 state 做序列化然后在 index.html 里插入如下的代码:有兴趣的同学可以去看下 readerToString 的源码
- 改造 client-bundle
index.html
已经 inject 了服务器端的 store 的 state 了,client 端拿这个值去初始化自己的 store 就可以啦。至此,场景2,这个页面是首屏的场景,得到了彻底的解决。完整的代码请参看 commit.
总结
以上就是 Vue SSR 处理首屏需要加载数据问题的思路和方案。基本上就是用 Vuex 为容器,以 index.html (序列化 state) 为载体来共享前后台数据,这个过程被 Vue SSR 官方文档形象的称为服务端数据预取。不过目前我们只解决了场景1,并没有解决场景2(页面不是首屏)。
我们可以这样体验一些场景2的问题,运行上面提到的示例代码后,我们首先访问 Home 页,然后跳转到 Test 页,此时 Test 页面不会加载数据。
下一篇,我们再来讨论这个问题。
系列文章: