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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。