Egg Vue SSR 服务端渲染数据请求与asyncData

惰性盆景
• 阅读 5359

Egg Vue SSR 服务端渲染数据请求与asyncData

服务端渲染 Node 层直接获取数据

在 Egg 项目如果使用模板引擎规范时通是过 render 方法进行模板渲染,render 的第一个参数模板路径,第二个参数时模板渲染数据. 如如下调用方式:

async index(ctx) {
    // 获取数据,可以是从数据库,后端 Http 接口 等形式
    const list = ctx.service.article.getArtilceList();
    // 对模板进行渲染,这里的 index.js 是 vue 文件通过 Webpack 构建的 JSBundle 文件
    await ctx.render('index.js', { list });
}

从上面的例子可以看出,这种使用方式是非常典型的也容易理解的模板渲染方式。在实际业务开发时,对于常规的页面渲染也建议使用这种方式获取数据没,然后进行页面渲染。Node 获取数据后,在 Vue 的根 Vue 文件里面就可以通过 this.list 的方式拿到 Node 获取的数据,然后就可以进行 vue 模板文件数据绑定了。


在这里有个高阶用法,可以直接把 ctx 等 Node 对象传递到 第二个参数里面,  这个时候你在模板里面就直接拿到 ctx 这些对象。 但这个时候就需要自己处理好 SSR 渲染时导致的 hydrate 问题,因为前端hydrate时并没有 ctx 对象。
 

async index(ctx) {
    // 获取数据,可以是从数据库,后端 Http 接口 等形式
    const list = ctx.service.article.getArtilceList();
    // 对模板进行渲染,这里的 index.js 是 vue 文件通过 Webpack 构建的 JSBundle 文件
    await ctx.render('index.js', { ctx, list });
}

服务端渲染 asyncData 方式获取数据

在 Vue 单页面 SSR 时涉及数据的请求方式,Node 层获取数据方式可以继续使用,但当路由切换时(页面直接刷新),Node 层就需要根据路由获取不同页面的数据,同时还要考虑前端路由切换的情况,这个时候路由是不会走 Node 层路由,而是直接进行的前端路由,这个时候也要考虑数据的请求方式。


基于以上使用的优雅问题,这里提供一种 asyncData 获取数据的方式解决单页面 SSR 刷新不走 SSR 问题。 Node 不直接获取数据,获取数据的代码直接写到前端代码里面。这里需要解决如下两个问题:

前端路由匹配 asyncData 调用

这里根据路由切换 url 获取指定的路由 componet 组件,然后检查是否有 aysncData,如果有就进行调用。调用之后,数据会放到 Vuex 的 store 里面。

return new Promise((resolve, reject) => {
        router.onReady(() => {
          // url 为当前请求路由,可以通过服务端传递到前端页面
          const matchedComponents = router.getMatchedComponents(url);
          if (!matchedComponents) {
            return reject({ code: '404' });
          }
          return Promise.all(
            matchedComponents.map(component => {
              // 关键代码
              if (component.methods && component.methods.asyncData) {
                return component.methods.asyncData(store);
              }
              return null;
            })
          ).then(() => {
            context.state = {
              ...store.state,
              ...context.state
            };
            return resolve(new Vue(options));
          });
        });
      });

Vue 模板定义 asyncData 方法

前端通过 Vuex 进行数据管理,把数据统一放到 store 里面,前端通过 this.$store.state 方式可以获取数据,Node 和 前端都可以获取到。

<script type="text/babel">
  export default{
    computed: {
      isLoading(){
       return false;
      },
      articleList() {
        return this.$store.state.articleList;
      }
    },
    methods: {
      asyncData ({ state, dispatch, commit }) {
        return dispatch('FETCH_ARTICLE_LIST')
      }
    }
  }
</script>

 

前端 asyncData 数据统一调用

在服务端 asyncData 调用时,可以解决单页面 SSR 刷新问题,那直接在前端切换路由时因不走服务端路由,那数据如何处理?


在 Vue 单页面实现时,通常都会使用 Vue-Router,这个时候可以借助 Vue-Router 提供 afterEach 钩子进行统一数据请求,可以直接调用 Vue 模板定义的 asyncData 方法。代码如下:

const options = this.create(window.__INITIAL_STATE__);
const { router, store } = options;
router.beforeEach((route, redirec, next) => {
  next();
});
router.afterEach((route, redirec) => {
  if (route.matched && route.matched.length) {
    const asyncData = route.matched[0].components.default.asyncData;
    if (asyncData) {
      asyncData(store);
    }
  }
});

最后贴上可以用的完整代码,请根据实际需要进行修改, 实际可运行例子见 https://github.com/easy-team/egg-vue-webpack-boilerplate/tree/feature/green/spa 

  • Vue 页面初始化统一封装
import Vue from 'vue';
import { sync } from 'vuex-router-sync';
import './vue/filter';
import './vue/directive';

export default class App {
  constructor(config) {
    this.config = config;
  }

  bootstrap() {
    if (EASY_ENV_IS_NODE) {
      return this.server();
    }
    return this.client();
  }

  create(initState) {
    const { index, options, createStore, createRouter } = this.config;
    const store = createStore(initState);
    const router = createRouter();
    sync(store, router);
    return {
      ...index,
      ...options,
      router,
      store
    };
  }

  client() {
    Vue.prototype.$http = require('axios');
    const options = this.create(window.__INITIAL_STATE__);
    const { router, store } = options;
    router.beforeEach((route, redirec, next) => {
      next();
    });
    router.afterEach((route, redirec) => {
      console.log('>>afterEach', route);
      if (route.matched && route.matched.length) {
        const asyncData = route.matched[0].components.default.asyncData;
        if (asyncData) {
          asyncData(store);
        }
      }
    });
    const app = new Vue(options);
    const root = document.getElementById('app');
    const hydrate = root.childNodes.length > 0;
    app.$mount('#app', hydrate);
    return app;
  }

  server() {
    return context => {
      const options = this.create(context.state);
      const { store, router } = options;
      router.push(context.state.url);
      return new Promise((resolve, reject) => {
        router.onReady(() => {
          const matchedComponents = router.getMatchedComponents();
          if (!matchedComponents) {
            return reject({ code: '404' });
          }
          return Promise.all(
            matchedComponents.map(component => {
              if (component.asyncData) {
                return component.asyncData(store);
              }
              return null;
            })
          ).then(() => {
            context.state = {
              ...store.state,
              ...context.state
            };
            return resolve(new Vue(options));
          });
        });
      });
    };
  }
}
  • 页面入口代码
// index.js
'use strict';
import App from 'framework/app.js';
import index from './index.vue';
import createStore from './store';
import createRouter from './router';

const options = { base: '/' };

export default new App({
  index,
  options,
  createStore,
  createRouter,
}).bootstrap();

  • 前端 router / store 定义
// store/index.js

'use strict';
import Vue from 'vue';
import Vuex from 'vuex';

import actions from './actions';
import getters from './getters';
import mutations from './mutations';

Vue.use(Vuex);

export default function createStore(initState = {}) {

  const state = {
    articleList: [],
    article: {},
    ...initState
  };

  return new Vuex.Store({
    state,
    actions,
    getters,
    mutations
  });
}

// router/index.js

import Vue from 'vue';

import VueRouter from 'vue-router';

import ListView from './list';

Vue.use(VueRouter);

export default function createRouter() {
  return new VueRouter({
    mode: 'history',
    base: '/',
    routes: [
      {
        path: '/',
        component: ListView
      },
      {
        path: '/list',
        component: ListView
      },
      {
        path: '/detail/:id',
        component: () => import('./detail')
      }
    ]
  });
}

点赞
收藏
评论区
推荐文章
CuterCorley CuterCorley
4年前
uni-app入门教程(3)数据绑定、样式绑定和事件处理
@toc前言本文的内容主要包含3部分:声明并渲染变量,包括条件渲染;通过class和style定义样式并动态绑定;事件的绑定,包含了事件传参。三部分均具有动态绑定的特性。一、模板语法及数据绑定1.声明和渲染变量在使用变量前,需要先声明,一般在data块中进行声明,如hellouniapp项目中index.vue中定义的title变
Wesley13 Wesley13
3年前
SSR再好,也要有优雅降级策略哟~
1、相关概念CSR:客户端渲染(ClientSideRender)。渲染过程全部交给浏览器进行处理,服务器不参与任何渲染。页面初始加载的HTML文档中无内容,需要下载执行JS文件,由浏览器动态生成页面,并通过JS进行页面交互事件与状态管理。SSR:服务端渲染(ServerSideRende
Easter79 Easter79
3年前
vue 生命周期的理解(created && mouted的区别)
生命周期先上图(笑skr个人~~)!(https://oscimg.oschina.net/oscnet/6d899c3576884ee9db73c0263ac9f4027e0.png)什么是生命周期Vue实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程,我
Stella981 Stella981
3年前
React 服务端渲染完美的解决方案
最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法。在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢?什么是服务器端渲染使用React构建客户端应用程序,默认情况下,可以在浏览器中输出React组件,进行生成DOM和操作DOM。Re
Wesley13 Wesley13
3年前
thinkphp3.2.3模板渲染支持三元表达式
thinkphp3.2.3模板渲染支持三元表达式{$status?'正常':'错误'}{$info'status'?$info'msg':$info'error'}注意:三元运算符中暂时不支持点语法。如下:           <divclass"modalhidefade"id'myModa
Stella981 Stella981
3年前
Spring Boot 与 Kotlin使用Spring
在《SpringBoot与Kotlin使用JdbcTemplate连接MySQL》中介绍了一种基本的数据访问方式,结合构建RESTfulAPI和使用Thymeleaf模板引擎渲染Web视图的内容就已经可以完成App服务端和Web站点的开发任务了。然而,在实际开发过程中,对数据库的操作无非就“增删改查”。就最为普遍的单表操作而言,除了表和字段不
Stella981 Stella981
3年前
Play之Scala
现在几乎每个web语言都会有这样那样的模板供你选择,如果你曾经使用过任何一种模板,我想模板这个概念你能很清晰的阐明,我借用类与对象的关系进行阐述:模板的功能就是将(含有模板元素的)页面实例化输出。每个人对模板的概念都不一而同,但模板干的事情几乎都是一致的渲染页面!Play的模板在HTML基础上直接基于Scala语言,模板文件通常存放在/app
Wesley13 Wesley13
3年前
Spring Boot 与 Kotlin 使用JdbcTemplate连接MySQL
之前介绍了一些Web层的例子,包括构建RESTfulAPI、使用Thymeleaf模板引擎渲染Web视图,但是这些内容还不足以构建一个动态的应用。通常我们做App也好,做Web应用也好,都需要内容,而内容通常存储于各种类型的数据库,服务端在接收到访问请求之后需要访问数据库获取并处理成展现给用户使用的数据形式。本文介绍在SpringBoot基础下配置数
Wesley13 Wesley13
3年前
Vue 服务端渲染(SSR)
Vue服务端渲染(SSR)什么是服务端渲染,简单理解是将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。于传统的SPA(单页应用)相比,服务端渲染能更好的有利于SEO,减少页面首屏加载时间,当然对开发来讲我们就不得不多学一些知识来支持服务端渲染。同时服务端渲染对服
Stella981 Stella981
3年前
JsRender 前端渲染模板基础学习
JsRender前端渲染模板使用模板,可以预先自定义一些固定格式的HTML标签,在需要显示数据时,再传入真实数据组装并展示在Web页中;避免了在JS中通过“”等手动分割、连接字符串的复杂过程;针对高性能和纯字符串渲染进行了优化;无需依赖DOM和jQuery;优先使用场景:元素重复出现;动态加载数据,并前端显示;JsR
惰性盆景
惰性盆景
Lv1
汴水流,泗水流,流到瓜洲古渡头,吴山点点愁。思悠悠,恨悠悠,恨到归时方始休,月明人倚楼。
文章
7
粉丝
0
获赞
0