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


Posted in Javascript onNovember 24, 2019

服务端渲染 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')
   }
  ]
 });
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
在JavaScript中使用inline函数的问题
Mar 08 Javascript
jQuery getJSON 处理json数据的代码
Jul 26 Javascript
jquery中ajax调用json数据的使用说明
Mar 17 Javascript
一个简单的jQuery计算器实现了连续计算功能
Jul 21 Javascript
Javascript点击其他任意地方隐藏关闭DIV实例
Jun 21 Javascript
整理一些最近经常遇到的前端面试题
Apr 25 Javascript
Angularjs cookie 操作实例详解
Sep 27 Javascript
详解react-router 4.0 下服务器如何配合BrowserRouter
Dec 29 Javascript
使用Angular CLI快速创建Angular项目的一些基本概念和写法小结
Apr 22 Javascript
详解extract-text-webpack-plugin 的使用及安装
Jun 12 Javascript
详解vue中axios请求的封装
Apr 08 Javascript
JS中比Switch...Case更优雅的多条件判断写法
Sep 05 Javascript
mpvue微信小程序开发之实现一个弹幕评论
Nov 24 #Javascript
node.js中Buffer缓冲器的原理与使用方法分析
Nov 23 #Javascript
node.js中事件触发器events的使用方法实例分析
Nov 23 #Javascript
javascript 原型与原型链的理解及实例分析
Nov 23 #Javascript
jquery 插件重新绑定的处理方法分析
Nov 23 #jQuery
微信小程序实现录音功能
Nov 22 #Javascript
小程序实现按下录音松开识别语音
Nov 22 #Javascript
You might like
php下连接mssql2005的代码
2011/01/17 PHP
drupal 代码实现URL重写
2011/05/04 PHP
php访问数组最后一个元素的函数end()用法
2015/03/18 PHP
PHP错误Warning:mysql_query()解决方法
2015/10/24 PHP
php mysql实现mysql_select_db选择数据库
2016/12/30 PHP
Ubuntu中支持PHP5与PHP7双版本的简单实现
2018/08/19 PHP
Laravel服务容器绑定的几种方法总结
2020/06/14 PHP
繁简字转换功能
2006/07/19 Javascript
javascript new一个对象的实质
2010/01/07 Javascript
JQuery入门—编写一个简单的JQuery应用案例
2013/01/03 Javascript
javascript修改表格背景色实例代码分享
2013/12/10 Javascript
node.js中的fs.lchown方法使用说明
2014/12/16 Javascript
全面了解javascript三元运算符
2016/06/27 Javascript
Ztree新增角色和编辑角色回显问题的解决
2016/10/25 Javascript
微信小程序 欢迎界面开发的实例详解
2016/11/30 Javascript
canvas实现图像截取功能
2017/02/06 Javascript
老生常谈jquery中detach()和remove()的区别
2017/03/02 Javascript
JavaScript获取URL参数的方法之一
2017/03/24 Javascript
JS生成随机打乱数组的方法示例
2017/12/23 Javascript
从零开始实现Vue简单的Toast插件
2018/12/03 Javascript
[09:23]国际邀请赛采访专栏:iG战队VK,Tongfu战队Cu
2013/08/05 DOTA
python解决字典中的值是列表问题的方法
2013/03/04 Python
跟老齐学Python之关于类的初步认识
2014/10/11 Python
Python 列表list使用介绍
2014/11/30 Python
python 连接各类主流数据库的实例代码
2018/01/30 Python
利用python为运维人员写一个监控脚本
2018/03/25 Python
Python3实现的反转单链表算法示例
2019/03/08 Python
python多线程并发实例及其优化
2019/06/27 Python
python获取指定日期范围内的每一天,每个月,每季度的方法
2019/08/08 Python
python列表的逆序遍历实现
2020/04/20 Python
Pycharm打开已有项目配置python环境的方法
2020/07/03 Python
销售员自我评价怎么写
2013/09/19 职场文书
土木工程个人自荐信范文
2013/11/30 职场文书
个人作风剖析材料
2014/02/02 职场文书
大型活动组织方案
2014/05/10 职场文书
会计实训报告范文
2014/11/04 职场文书