美文网首页
数据预取和状态

数据预取和状态

作者: 翔子丶 | 来源:发表于2021-05-10 08:13 被阅读0次

接上篇管理头部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 压缩

相关文章

  • 数据预取和状态

    接上篇管理头部Head内容[https://www.jianshu.com/p/794b9b4b8195] 官方文...

  • vue-服务端渲(ssr)

    几个概念 基本用法 通用代码 源码结构 路由和代码分割 数据预取和状态 数据仓库代码拆分 激客户端 Vue 在 浏...

  • React如何在Server Side Rendering中预取

    前一篇文章讲到了为了预取数据,各个组件的写法。这里从整体上讲一个client和server分别应该怎么做。Serv...

  • Celery笔记

    Celery 预取机制 Celery 默认启动预取机制,即如果有多个worker,会平均分配给多个worker,如...

  • redis高级功能-队列

    redis队列实现高并发下数据ID读取 id数据预生成id数据预生成:根据业务逻辑,预生成ID数据,通过redis...

  • Tensorflow 数据预读取--Queue

    Google开源的深度学习框架Tensorflow在数据预取上做了一些特殊的特征来提高模型训练或者推理的效率,避免...

  • 面向产业的深度学习代码结构

    描述任务,即任务简介和基础知识 搭建开发环境 准备数据,导入并完成数据增强 加载预训练模型 迁移学习并微调模型 预...

  • 9.数据的异步ajax

    前言 这节我们将实现数据的异步获取,主要用到了: redux-async-connect:数据异步取,并将状态放入...

  • 数据保护和数据爬取

    数据的保护和爬取好比是矛和盾。数据需要保护,但是数据产生之后会有大量的爬取需求。如何实现数据的保护和爬取值得我们思...

  • Scrapy+Selenium+Headless Chrome的

    前言 展示如何使用Scrapy爬取静态数据和Selenium+Headless Chrome爬取JS动态生成的数据...

网友评论

      本文标题:数据预取和状态

      本文链接:https://www.haomeiwen.com/subject/zzxihltx.html