这个系列终于要结束啦。😂 上篇中,我们留了最后一个问题: 需要获取数据的 页面不是首屏
的时候,如何处理数据的请求。官方文档这一节叫 客户端数据预取(Client Data Fetching), 然而这一节的内容实际上并不像上一节 客户端数据预取 那样复杂。
面临的问题
重新审视一下我们的问题:我们要为这个页面添加前端的数据获取逻辑,这个逻辑在页面作为首屏的时候不能起作用,最好能复用后台获取数据的代码。
- 前端的数据获取逻辑,单解决这个问题很容易,我们在 client-side only 项目里,已经有了充分的实践。vue router 文档里也写的很清楚。
- 作为首屏的时候不能起作用,这个用 client only 的项目里,我们就不知道怎么处理了,因为首屏可能涉及很多的组件,我们让组件能够区分它们是否是在首屏中加载。
- 复用后台获取数据的代码,为了做服务端数据预取的时候,我们为组件添加了
asyncData
的静态方法,如果客户端获取数据的时候能复用这些代码就最好了,否则这些逻辑需要重写。
Vue SSR 的解决方案
和 client-side only 的项目 vue-router 数据获取类似,Vue SSR 官方也提供了2种方案,而且尤大已经阐述的很清楚了,建议大家直接跳过去深入学习。这里为了这个系列的完整性,我贴出其中在路由导航之前解析数据这种场景的代码,并稍作解释:
// client-bundle.js
import { createApp } from './app'
const { app, router, store } = createApp()
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
router.onReady(() => {
// 这里假定 App.vue 模板中根元素具有 id="app"
app.$mount('#app')
+ // 添加路由钩子函数,用于处理 asyncData.
+ // 在初始路由 resolve 后执行,
+ // 以便我们不会二次预取(double-fetch)已有的数据。
+ // 使用 `router.beforeResolve()`,以便确保所有异步组件都 resolve。
+① router.beforeResolve((to, from , next) => {
+ const matched = router.getMatchedComponents(to)
+ const prevMatched = router.getMatchedComponents(from
+ // 我们只关心非预渲染的组件
+ // 所以我们对比它们,找出两个匹配列表的差异组件
+ let diffed = false
+② const activated = matched.filter((c, i) => {
+ return diffed || (diffed = (prevMatched[i] !== c))
+ })
+ if (!activated.length) {
+ return next()
+ }
+ // 这里如果有加载指示器(loading indicator),就触发
+③ Promise.all(activated.map(c => {
+ if (c.asyncData) {
+ alert('fetch data in client side')
+ return c.asyncData({ store, route: to })
+ }
+ })).then(() => {
+ // 停止加载指示器(loading indicator)
+ next()
+ }).catch(next)
+ })
})
-
这个方案和后台的方案基本类似,思路就是在导航之前去调用所有尚未加载组件的
asyncData
方法,待所有数据都请求完成后,再跳到页面。③ -
代码②这段,对比现有路由的组件 (preMatched) 和目标路由的组件(matched),从中找出不同的组件, 以避免不必要的数据请求。比如下图的场景, User 组件的 asyncData 就不会调用了。
/user/foo/profile /user/foo/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+
-
代码①这个位置的
router.beforeResolve
回调是 Vue router 专门为 SSR 做的新 路由守卫函数,其触发时机是在所有组件内守卫和异步路由组件被解析之后,以及导航被确认之前。在这个钩子中我们可以充分的访问各个异步组件。前台路由导航放在这个回调里,确保了上提到的 1, 2 两点可以进行。 -
最后,最值得注意的地方是
router.beforeResolve
这个钩子注册是在 router.ready 之后的。也就是说是在首屏组件全部确认之后,所有对于首屏来说router.beforeResolve
里的回调并不会触发。
以上几点,保证了前文提出的所有3个问题的解决: 前端的数据获取逻辑,作为首屏的时候不能起作用,复用后台获取数据的代码.
看一眼我们的成果:
完整的代码请参看 commit.
总结
到此为止,我们完成了一个非常基本的 Vue SSR 框架的搭建。当然 Vue SSR 还有更多高级的内容,比如 hot reload, sourcemap, cache 等,如果大家需要在项目中用的话,则需要进一步阅读官方指南并深入研究,或者直接使用目前已经比较成熟的 Vue SSR 框架 Nuxt.js. 正如尤大说的(官方指南中的)解决方案不是限定的 - 我们发现它们对我们来说很好,但这并不意味着无法继续改进, 这些技术方案都不是死的,也不一定是永远最好的,如果你有自己的需求,并且你能理解 SSR 最基本的问题和解决思路,那么你完全可以制定适合自己需求的,其他更好的方案。
系列文章: