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 相关文章推荐
浅谈jquery.fn.extend与jquery.extend区别
Jul 13 Javascript
详解JavaScript中的属性和特性
Dec 08 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
Feb 16 Javascript
JS实现的五级联动菜单效果完整实例
Feb 23 Javascript
jQuery EasyUI tree增加搜索功能的实现方法
Apr 27 jQuery
JS HTML图片显示Canvas 压缩功能
Jul 21 Javascript
微信小程序实现顶部选项卡(swiper)
Jun 19 Javascript
详解基于vue-router的动态权限控制实现方案
Sep 28 Javascript
vue toggle做一个点击切换class(实例讲解)
Mar 13 Javascript
vue路由拦截及页面跳转的设置方法
May 24 Javascript
Vue中keep-alive组件的深入理解
Aug 23 Javascript
JavaScript常用进制转换及位运算实例解析
Oct 14 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+MySql编写聊天室
2006/10/09 PHP
PHP队列用法实例
2014/11/05 PHP
php调用mysql存储过程实例分析
2014/12/29 PHP
php限制上传文件类型并保存上传文件的方法
2015/03/13 PHP
最准确的php截取字符串长度函数
2015/10/29 PHP
Yii2框架类自动加载机制实例分析
2018/05/02 PHP
jQuery实用技巧必备(中)
2015/11/03 Javascript
jQuery处理XML文件的几种方法
2016/06/14 Javascript
EasyUI在表单提交之前进行验证的实例代码
2016/06/24 Javascript
详解为Angular.js内置$http服务添加拦截器的方法
2016/12/20 Javascript
vue实现弹框遮罩点击其他区域弹框关闭及v-if与v-show的区别介绍
2018/09/29 Javascript
python原始套接字编程示例分享
2014/02/21 Python
详尽讲述用Python的Django框架测试驱动开发的教程
2015/04/22 Python
利用Python实现Windows下的鼠标键盘模拟的实例代码
2017/07/13 Python
详解Python import方法引入模块的实例
2017/08/02 Python
python交互式图形编程实例(三)
2017/11/17 Python
Python产生Gnuplot绘图数据的方法
2018/11/09 Python
Python利用sqlacodegen自动生成ORM实体类示例
2019/06/04 Python
selenium跳过webdriver检测并模拟登录淘宝
2019/06/12 Python
Django 响应数据response的返回源码详解
2019/08/06 Python
python 发送json数据操作实例分析
2019/10/15 Python
python输出第n个默尼森数的实现示例
2020/03/08 Python
keras自定义回调函数查看训练的loss和accuracy方式
2020/05/23 Python
python下载的库包存放路径
2020/07/27 Python
Python txt文件常用读写操作代码实例
2020/08/03 Python
Python数据可视化常用4大绘图库原理详解
2020/10/23 Python
cookies应对python反爬虫知识点详解
2020/11/25 Python
细说CSS3中box属性中的overflow-x属性和overflow-y属性值的效果
2014/07/21 HTML / CSS
eBay法国购物网站:eBay.fr
2017/10/21 全球购物
开水果连锁店创业计划书
2013/12/29 职场文书
运动会稿件50字
2014/02/17 职场文书
销售行政专员岗位职责
2014/06/10 职场文书
2014年客房部工作总结
2014/11/22 职场文书
Nginx下SSL证书安装部署步骤介绍
2021/12/06 Servers
MySQL之MyISAM存储引擎的非聚簇索引详解
2022/03/03 MySQL
使用python创建股票的时间序列可视化分析
2022/03/03 Python