Egg Vue SSR 服务端渲染数据请求与asyncData
服务端渲染Node层直接获取数据
在Egg项目如果使用模板引擎规范时通是过render方法进行模板渲染,render的第一个参数模板路径,第二个参数时模板渲染数据.如如下调用方式:
asyncindex(ctx){ //获取数据,可以是从数据库,后端Http接口等形式 constlist=ctx.service.article.getArtilceList(); //对模板进行渲染,这里的index.js是vue文件通过Webpack构建的JSBundle文件 awaitctx.render('index.js',{list}); }
从上面的例子可以看出,这种使用方式是非常典型的也容易理解的模板渲染方式。在实际业务开发时,对于常规的页面渲染也建议使用这种方式获取数据没,然后进行页面渲染。Node获取数据后,在Vue的根Vue文件里面就可以通过this.list的方式拿到Node获取的数据,然后就可以进行vue模板文件数据绑定了。
在这里有个高阶用法,可以直接把ctx等Node对象传递到第二个参数里面, 这个时候你在模板里面就直接拿到ctx这些对象。但这个时候就需要自己处理好SSR渲染时导致的hydrate问题,因为前端hydrate时并没有ctx对象。
asyncindex(ctx){ //获取数据,可以是从数据库,后端Http接口等形式 constlist=ctx.service.article.getArtilceList(); //对模板进行渲染,这里的index.js是vue文件通过Webpack构建的JSBundle文件 awaitctx.render('index.js',{ctx,list}); }
服务端渲染asyncData方式获取数据
在Vue单页面SSR时涉及数据的请求方式,Node层获取数据方式可以继续使用,但当路由切换时(页面直接刷新),Node层就需要根据路由获取不同页面的数据,同时还要考虑前端路由切换的情况,这个时候路由是不会走Node层路由,而是直接进行的前端路由,这个时候也要考虑数据的请求方式。
基于以上使用的优雅问题,这里提供一种asyncData获取数据的方式解决单页面SSR刷新不走SSR问题。Node不直接获取数据,获取数据的代码直接写到前端代码里面。这里需要解决如下两个问题:
前端路由匹配asyncData调用
这里根据路由切换url获取指定的路由componet组件,然后检查是否有aysncData,如果有就进行调用。调用之后,数据会放到Vuex的store里面。
returnnewPromise((resolve,reject)=>{ router.onReady(()=>{ //url为当前请求路由,可以通过服务端传递到前端页面 constmatchedComponents=router.getMatchedComponents(url); if(!matchedComponents){ returnreject({code:'404'}); } returnPromise.all( matchedComponents.map(component=>{ //关键代码 if(component.methods&&component.methods.asyncData){ returncomponent.methods.asyncData(store); } returnnull; }) ).then(()=>{ context.state={ ...store.state, ...context.state }; returnresolve(newVue(options)); }); }); });
Vue模板定义asyncData方法
前端通过Vuex进行数据管理,把数据统一放到store里面,前端通过this.$store.state方式可以获取数据,Node和前端都可以获取到。
exportdefault{ computed:{ isLoading(){ returnfalse; }, articleList(){ returnthis.$store.state.articleList; } }, methods:{ asyncData({state,dispatch,commit}){ returndispatch('FETCH_ARTICLE_LIST') } } }
前端asyncData数据统一调用
在服务端asyncData调用时,可以解决单页面SSR刷新问题,那直接在前端切换路由时因不走服务端路由,那数据如何处理?
在Vue单页面实现时,通常都会使用Vue-Router,这个时候可以借助Vue-Router提供afterEach钩子进行统一数据请求,可以直接调用Vue模板定义的asyncData方法。代码如下:
constoptions=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){ constasyncData=route.matched[0].components.default.asyncData; if(asyncData){ asyncData(store); } } });
最后贴上可以用的完整代码,请根据实际需要进行修改,实际可运行例子见https://github.com/easy-team/egg-vue-webpack-boilerplate/tree/feature/green/spa
Vue页面初始化统一封装
importVuefrom'vue'; import{sync}from'vuex-router-sync'; import'./vue/filter'; import'./vue/directive'; exportdefaultclassApp{ constructor(config){ this.config=config; } bootstrap(){ if(EASY_ENV_IS_NODE){ returnthis.server(); } returnthis.client(); } create(initState){ const{index,options,createStore,createRouter}=this.config; conststore=createStore(initState); constrouter=createRouter(); sync(store,router); return{ ...index, ...options, router, store }; } client(){ Vue.prototype.$http=require('axios'); constoptions=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){ constasyncData=route.matched[0].components.default.asyncData; if(asyncData){ asyncData(store); } } }); constapp=newVue(options); constroot=document.getElementById('app'); consthydrate=root.childNodes.length>0; app.$mount('#app',hydrate); returnapp; } server(){ returncontext=>{ constoptions=this.create(context.state); const{store,router}=options; router.push(context.state.url); returnnewPromise((resolve,reject)=>{ router.onReady(()=>{ constmatchedComponents=router.getMatchedComponents(); if(!matchedComponents){ returnreject({code:'404'}); } returnPromise.all( matchedComponents.map(component=>{ if(component.asyncData){ returncomponent.asyncData(store); } returnnull; }) ).then(()=>{ context.state={ ...store.state, ...context.state }; returnresolve(newVue(options)); }); }); }); }; } }
页面入口代码
//index.js 'usestrict'; importAppfrom'framework/app.js'; importindexfrom'./index.vue'; importcreateStorefrom'./store'; importcreateRouterfrom'./router'; constoptions={base:'/'}; exportdefaultnewApp({ index, options, createStore, createRouter, }).bootstrap();
前端router/store定义
//store/index.js 'usestrict'; importVuefrom'vue'; importVuexfrom'vuex'; importactionsfrom'./actions'; importgettersfrom'./getters'; importmutationsfrom'./mutations'; Vue.use(Vuex); exportdefaultfunctioncreateStore(initState={}){ conststate={ articleList:[], article:{}, ...initState }; returnnewVuex.Store({ state, actions, getters, mutations }); } //router/index.js importVuefrom'vue'; importVueRouterfrom'vue-router'; importListViewfrom'./list'; Vue.use(VueRouter); exportdefaultfunctioncreateRouter(){ returnnewVueRouter({ mode:'history', base:'/', routes:[ { path:'/', component:ListView }, { path:'/list', component:ListView }, { path:'/detail/:id', component:()=>import('./detail') } ] }); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。