Vue 服务端渲染的数据流

概述

面试中被问到了服务端渲染的数据流动,没答上来,复盘整理一下,供以后工作时参考,相信对其他人也有用。

vue ssr 指南

数据流动

主要分以下几种情况:

1.初次打开页面时,会在服务端获取 matched 路由,然后执行他们的 asyncData 方法,这个方法会把获取的数据存放到 vuex 的 store 里面去,在全部获取完成之后把数据附加到渲染上下文中,状态将自动序列化为window.__INITIAL_STATE__,并注入 HTML,在客户端挂在应用之前,store就获取到了状态:

// entry-server.js
import { createApp } from './app'

export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()

    router.push(context.url)

    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }

      // 对所有匹配的路由组件调用 `asyncData()`
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        // 在所有预取钩子(preFetch hook) resolve 后,
        // 我们的 store 现在已经填充入渲染应用程序所需的状态。
        // 当我们将状态附加到上下文,
        // 并且 `template` 选项用于 renderer 时,
        // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
        context.state = store.state

        resolve(app)
      }).catch(reject)
    }, reject)
  })
}

2.当在浏览器端改变当前路由的 params 的时候,直接请求本路由下面所有的 asyncData 方法,并更新 store。

// entry-client.js
// a global mixin that calls `asyncData` when a route component's params change
Vue.mixin({
  beforeRouteUpdate (to, from, next) {
    const { asyncData } = this.$options
    if (asyncData) {
      asyncData({
        store: this.$store,
        route: to
      }).then(next).catch(next)
    } else {
      next()
    }
  }
})

注意:beforeRouteUpdate这个钩子在当前路由改变,当时该组件被复用时调用。(初次加载的时候不会执行这个钩子,因为组件没有被复用)

3.挡在浏览器端从一个路由跳到另一个路由时,通过比较前后匹配到的路由,对于不同的路由,请求它们下面的 asyncData 方法,并更新 store。

// entry-client.js
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))
    })
    const asyncDataHooks = activated.map(c => c.asyncData).filter(_ => _)
    if (!asyncDataHooks.length) {
      return next()
    }

    bar.start()
    Promise.all(asyncDataHooks.map(hook => hook({ store, route: to })))
      .then(() => {
        bar.finish()
        next()
      })
      .catch(next)
})

注意:beforeResolve这个钩子是全局解析守卫,会在路由变化时调用,并且在所有组件内守卫和异步路由组件被解析之后才调用。(初次加载的时候也不会加载这个钩子,因为这个钩子是在router.onReady之后才加入的)

asyncData 和 beforeMount

在客户端有 2 种方法来获取数据,它们都能够随意使用,但是用户体验不同:

1.在 asyncData 里面获取,就像上面所介绍的,它会使 app 在拿到数据后才跳转路由。所以在拿到数据的过程中会开启 progressbar 来告诉用户正在发请求。

2.在 beforeMount 里面获取,它会先跳转路由,然后再获取数据。所以在跳转路由之后,需要在展示的位置放一个 spinner 来告诉用户正在加载数据。

原文地址:https://www.cnblogs.com/yangzhou33/p/13759484.html