数据预取和状态

接上篇管理头部Head内容

官方文档很难看懂,可以通过一个实际需求来了解服务端渲染中的数据预取和状态管理

需求:实现通过服务端渲染的方式来把异步接口数据渲染到页面中

如果是纯客户端渲染,无非就是在页面发请求拿数据,然后在模板中遍历出来,但是想要通过服务端渲染的方式来处理的话就比较麻烦。

使用服务端渲染(服务端获取异步接口数据,交给 Vue 组件去渲染)流程:

image-20210329113524310.png

首先想到的肯定是在组件的生命周期钩子中请求获取数据渲染页面,创建pages/Posts.vue组件

<template>
  <div>
    <h1>Post List</h1>
    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'PostList',
  data () {
    return {
      posts: []
    }
  },
  // 服务端渲染 只支持 beforeCreate 和 created
  // 不会等待 beforeCreate 和 created 中的异步操作
  // 不支持响应式数据
  // 所有这种做法在服务端渲染中是不会工作的!!!
  async created () {
    console.log('Posts Created Start')
    const { data } = await axios({
      method: 'GET',
      url: 'https://cnodejs.org/api/v1/topics'
    })
    this.posts = data.data
    console.log('Posts Created End')
  }
}
</script>

此时虽然页面可以正常加载,但是可以看到不是首次渲染就渲染好的html片段,而是通过接口请求到的数据

接着我们按照官方文档给出的参考来把服务端渲染中的数据预取以及状态管理来处理一下。

核心思路就是把在服务端渲染期间获取的数据存储到 Vuex 容器中, 然后把容器中的数据同步到客户端,这样就保持了前后端渲染的数据状态同步,避免了客户端重新渲染 的问题。

  1. 通过Vuex创建容器实例,并挂载到Vue实例

    // store/index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    import axios from 'axios'
    Vue.use(Vuex)
    export const createStore = () => {
        return new Vuex.Store({
            state: {
                posts: [] // 文章列表
            },
            mutations: {
                // 修改容器状态
                setPosts (state, data) {
                    state.posts = data
                }
            },
            actions: {
                async getPosts ({ commit }) {
                    const { data } = await axios({
                        method: 'GET',
                        url: 'https://cnodejs.org/api/v1/topics'
                    })
                    commit('setPosts', data.data)
                }
            }
        })
    }
    
  2. 在通用应用入口中将 Vuex 容器挂载到 Vue 根实例

    // app.js
    ...import { createStore } from './store'
    ...
    export function createApp () {
        const router = createRouter()
        const store = createStore()
        const app = new Vue({
            router, // 把路由挂载到 Vue 根实例中
            store, // 把容器挂载到 Vue 根实例
            // 根实例简单的渲染应用程序组件。
            render: h => h(App)
        })
        return { app, router, store }
    }
    
  3. 在组件中使用 serverPrefetch 触发容器中的 action

    <template>
      <div>
        <h1>Post List</h1>
        <ul>
          <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
        </ul>
      </div>
    </template>
    
    <script>
    import { mapState, mapActions } from 'vuex'
    
    export default {
      name: 'PostList',
      metaInfo: {
        title: 'Posts'
      },
      computed: {
        ...mapState(['posts'])
      },
    
      // Vue SSR 特殊为服务端渲染提供的一个生命周期钩子函数
      serverPrefetch () {
        // 一定按照这种形式:发起 action,返回 Promise
        // this.$store.dispatch('getPosts')
        return this.getPosts()
      },
      methods: {
        ...mapActions(['getPosts'])
      }
    }
    </script>
    
  4. 在服务端渲染应用入口中将容器状态序列化到页面中

    接下来我们要做的就是把在服务端渲染期间所获取填充到容器中的数据同步到客户端容器中,从而避免两个端状态不一致导致客户端重新渲染的问题。

    // entry-server.js
    router.onReady...
    context.rendered = () => {
        // 在应用渲染完成以后,服务端 Vuex 容器中已经填充了状态数据
        // 这里手动的把容器中的状态数据放到 context 上下文中
        // Renderer 在渲染页面模板的时候会把 state 序列化为字符串串内联到页面中
        // window.__INITIAL_STATE__ = store.state
        context.state = store.state
    }
    

    两个端数据状态没有同步,还需设置客户端数据状态

    image-20210329083452856.png
  1. 最后,在客户端渲染入口中把服务端传递过来的状态数据填充到客户端 Vuex 容器中

    // entry-client.js
    import { createApp } from './app'
    // 客户端特定引导逻辑……
    const { app, router, store } = createApp()
    // 如果当前页面中有 __INITIAL_STATE__ 数据,则直接将其填充到客户端容器中
    if (window.__INITIAL_STATE__) {
        // We initialize the store state with the data injected from the server
        store.replaceState(window.__INITIAL_STATE__)
    }
    router.onReady(() => {
        app.$mount('#app')
    })
    

项目地址

服务端渲染优化

尽管Vue的SSR速度相当快,但由于创建组件实例和虚拟DOM节点的成本,它无法与纯基于字符串的模板的性能相匹配。在SSR性能至关重要的情况下,明智的利用缓存策略可极大的缩短响应时间并减少服务器负载。

页面级别缓存

官方文档中介绍的那样,对特定的页面合理的应用 micro-caching 能够大大改善服务器处理并发的能力(吞吐率 RPS )。

但并非所有页面都适合使用 micro-caching 缓存策略,我们可以将资源分为三类:

  • 静态资源:如 js 、 css 、 images 等。
  • 用户特定的动态资源:不同的用户访问相同的资源会得到不同的内容。
  • 用户无关的动态资源:任何用户访问该资源都会得到相同的内容,但该内容可能在任意时间发生变 化,如博客文章。

只有“用户无关的动态资源”适合应用 micro-caching 缓存策略。

使用lru-cache

npm install lru-cache --save

修改server.js

...
const LRU = require('lru-cache')

const cache = new LRU({
  max: 100,
  maxAge: 10000, // Important: entries expires after 1 second.
})
const isCacheable = (req) => {
  console.log(req.url)
  if (req.url === '/posts') {
    return true
  }
}
...
const render = async (req, res) => 
  try {
    const cacheable = isCacheable(req)
    if (cacheable) {
      const html = cache.get(req.url)
      if (html) {
        return res.end(html)
      }
    }
    const html = await renderer.renderToString({
      title: '拉钩教育',
      meta: `
        <meta name="description" content="拉钩">
      `,
      url: req.url,
    })
    res.setHeader('Content-type', 'text/html;charset=utf8')
    res.end(html)
    
    if (cacheable) {
      cache.set(req.url, html)
    }
  } catch (e) {
    res.status(500).end('Internal Srever ERROR')
  }
}
...

由于内容缓存只有一秒钟,用户将无法查看过期的内容。然而,这意味着,对于每个要缓存的页面,服务器最多只能每秒执行一次完整渲染。

组件级别缓存

Vue SSR内置支持组件级别缓存,在创建renderer时传入cache开启缓存

官方案例

Gzip 压缩
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 前端常见的一些问题 1.前端性能优化手段? 1. 尽可能使用雪碧图2. 使用字体图标代替图片3. 对HTML,cs...
    十八人言阅读 1,107评论 0 1
  • 1、说说你对 SPA 单页面的理解,它的优缺点分别是什么? SPA( single-page applicatio...
    她与星河皆遗憾阅读 412评论 0 2
  • 前言 本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性、框架的原理以问题的形式进行整理汇总,意在帮助作...
    lessonSam阅读 376评论 0 0
  • 前言 本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性、框架的原理以问题的形式进行整理汇总,意在帮助作...
    我的章鱼小丸子呢阅读 578评论 0 1
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,551评论 0 11