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 相关文章推荐
JS去除右边逗号的简单方法
Jul 03 Javascript
js setTimeout 常见问题小结
Aug 13 Javascript
js字符串完全替换函数分享
Dec 03 Javascript
js的window.showModalDialog及window.open用法实例分析
Jan 29 Javascript
AngularJS过滤器filter用法实例分析
Nov 04 Javascript
利用Vue v-model实现一个自定义的表单组件
Apr 27 Javascript
解决webpack无法通过IP地址访问localhost的问题
Feb 22 Javascript
React SSR样式及SEO的实践
Oct 22 Javascript
在Bootstrap开发框架中使用dataTable直接录入表格行数据的方法
Oct 25 Javascript
JavaScript常见鼠标事件与用法分析
Jan 03 Javascript
Vue使用Clipboard.JS在h5页面中复制内容实例详解
Sep 03 Javascript
微信小程序按钮点击动画效果的实现
Sep 04 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
冰滴咖啡制作步骤
2021/03/03 冲泡冲煮
PHP5在Apache下的两种模式的安装
2006/09/05 PHP
php动态生成JavaScript代码
2009/03/09 PHP
自动把纯文本转换成Web页面的php代码
2009/08/27 PHP
在PHP中使用redis
2013/11/04 PHP
PHP中new static() 和 new self() 的区别介绍
2015/01/09 PHP
thinkphp框架实现删除和批量删除
2016/06/29 PHP
thinkPHP利用ajax异步上传图片并显示、删除的示例
2018/09/26 PHP
php实现根据身份证获取精准年龄
2020/02/26 PHP
在一个form用一个SUBMIT(或button)分别提交到两个处理表单页面的代码
2007/02/15 Javascript
js修改原型的属性使用介绍
2014/01/26 Javascript
javascript删除数组元素并且数组长度减小的简单实例
2014/02/14 Javascript
JavaScript代码编写中各种各样的坑和填坑方法
2014/06/06 Javascript
ECMAScript6函数默认参数
2015/06/12 Javascript
Javascript验证Visa和MasterCard信用卡号的方法
2015/07/27 Javascript
bootstrap-wysiwyg结合ajax实现图片上传实时刷新功能
2016/05/27 Javascript
jQuery判断是否存在滚动条的简单方法
2016/09/17 Javascript
jQuery实现标签页效果实战(4)
2017/02/08 Javascript
vue使用watch 观察路由变化,重新获取内容
2017/03/08 Javascript
js实现控制文件拖拽并获取拖拽内容功能
2018/02/17 Javascript
解决vue select当前value没有更新到vue对象属性的问题
2018/08/30 Javascript
layui中的switch开关实现方法
2019/09/03 Javascript
Python 解析XML文件
2009/04/15 Python
python中使用百度音乐搜索的api下载指定歌曲的lrc歌词
2014/07/18 Python
python 2.7.14安装图文教程
2018/04/08 Python
windows下numpy下载与安装图文教程
2019/04/02 Python
python数据分析:关键字提取方式
2020/02/24 Python
可打印的优惠券、杂货和优惠券代码:Coupons.com
2018/06/12 全球购物
Mamaearth官方网站:印度母婴护理产品公司
2019/10/06 全球购物
数控加工专业毕业生自荐信
2013/09/27 职场文书
教师职称自我鉴定
2014/02/12 职场文书
中秋节国旗下演讲稿
2014/09/13 职场文书
夫妻分居协议书范本(有子女版)
2014/11/01 职场文书
业务员年终工作总结2015
2015/05/28 职场文书
python通过新建环境安装tfx的问题
2022/05/20 Python
JS前端使用canvas实现扩展物体类和事件派发
2022/08/05 Javascript