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的文字滚动跑马灯插件(一个页面多个滚动区)
Jul 26 Javascript
JS重要知识点小结
Nov 06 Javascript
html中的input标签的checked属性jquery判断代码
Sep 19 Javascript
dwz 如何去掉ajaxloading具体代码
May 22 Javascript
分享一个常用的javascript静态类
Dec 31 Javascript
javascript每日必学之多态
Feb 23 Javascript
JavaScript直播评论发弹幕切图功能点集合效果代码
Jun 26 Javascript
vue 实现的树形菜的实例代码
Mar 19 Javascript
详解如何配置vue-cli3.0的vue.config.js
Aug 23 Javascript
jQuery.validate.js表单验证插件的使用代码详解
Oct 22 jQuery
Javascript的this详解
Mar 23 Javascript
使用Vue.js中的过滤器实现幂方求值的方法
Aug 27 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实现阳历阴历互转的方法
2015/10/28 PHP
php 截取utf-8格式的字符串实例代码
2016/10/30 PHP
【消息提示组件】,兼容IE6/7&amp;&amp;FF2
2007/09/04 Javascript
如何使用Javascript正则表达式来格式化XML内容
2013/07/04 Javascript
jQuery中:header选择器用法实例
2014/12/29 Javascript
JavaScript阻止浏览器返回按钮的方法
2015/03/18 Javascript
基于JavaScript实现全屏透明遮罩div层锁屏效果
2016/01/26 Javascript
jquery插件bootstrapValidator表单验证详解
2016/12/15 Javascript
JavaScript实现的XML与JSON互转功能详解
2017/02/16 Javascript
vue.js之vue-cli脚手架的搭建详解
2017/05/05 Javascript
AngularJS select加载数据选中默认值的方法
2018/02/28 Javascript
JS实现根据数组对象的某一属性排序操作示例
2019/01/14 Javascript
小程序开发踩坑:页面窗口定位(相对于浏览器定位)(推荐)
2019/04/25 Javascript
生产制造追溯系统之再说条码打印
2019/06/03 Javascript
jQuery实现图片随机切换、抽奖功能(实例代码)
2019/10/23 jQuery
vue.js 子组件无法获取父组件store值的解决方式
2019/11/08 Javascript
JavaScript中变量提升机制示例详解
2019/12/27 Javascript
深入理解Antd-Select组件的用法
2020/02/25 Javascript
vue中的循环对象属性和属性值用法
2020/09/04 Javascript
浅谈Vue开发人员的7个最好的VSCode扩展
2021/01/20 Vue.js
对Python新手编程过程中如何规避一些常见问题的建议
2015/04/01 Python
python输入错误密码用户锁定实现方法
2017/11/27 Python
Python 在字符串中加入变量的实例讲解
2018/05/02 Python
python 随机生成10位数密码的实现代码
2019/06/27 Python
Python实现Selenium自动化Page模式
2019/07/14 Python
Python 基于wxpy库实现微信添加好友功能(简洁)
2019/11/29 Python
python3.8下载及安装步骤详解
2020/01/15 Python
少年闰土教学反思
2014/02/22 职场文书
第一批党的群众路线教育实践活动工作总结
2014/03/03 职场文书
优秀中职教师事迹材料
2014/08/26 职场文书
研修心得体会
2014/09/04 职场文书
2015年资料员工作总结
2015/04/25 职场文书
推荐六本经典文学奖书籍:此生必读
2019/08/22 职场文书
vue完美实现el-table列宽自适应
2021/05/08 Vue.js
python数字图像处理:图像的绘制
2022/06/28 Python