(我来水经验了×,因为马上要去美团实习了,技术栈是vue2 + ts,在看vue-router文档的时候看到了关于路由组件获取异步数据的两种方式的总结,以前做业务都是无脑写,还真没总结过,所以简单写个vue2 + class-component + ts的demo,包括一些技术栈使用相关的细节也会记录一下,如果不感兴趣跳过就行
路由组件获取数据方式总结
某个路由组件需要通过网络请求获取服务端数据,其实无非就两种方式:第一种即在组件内获取,比如在created
钩子中发请求;第二种就是在beforeRouteEnter
路由守卫中。
两种方式从逻辑来讲都是没有问题的,主要的差别就在于用户体验的不同:生命周期中获取异步数据,用户会第一时间转跳到路由视图,往往需要搭配一些条件渲染的逻辑来优化数据获取之前的路由视图(比如数据渲染前展示Loading
);路由守卫中获取异步数据的话,在next
调用之前会一直停留在当前页面而不会进入路由视图,自然也需要一些类似于Loading
或者progress
的反馈组件来优化体验。
下面简单实现一下上面的两种情形,适合前端小白入门以及熟悉技术栈来食用,当作一个学习demo练手的话可以跟着操作一下
项目搭建
vue-cli创建项目:
arduino
vue create vue2-class-component
选择Manually select features
,选择Typescript
以及vue2.x + class-style component
,其它关于lint的选项就无所谓了
安装vue-router
如果是vue2项目,安装vue-router一定不要安装vue-router@4
,4版本是对应vue3的
所以安装依赖:
css
pnpm i vue-router@3.5.2
@/router/index.ts
:
ts
+ import Home from '@/pages/Home.vue';
+ import VueRouter from 'vue-router';
+ import Vue from 'vue';
+ Vue.use(VueRouter);
+ const routes = [
+ {
+ path: '/',
+ redirect: '/home'
+ },
+ {
+ path: '/home',
+ component: Home
+ }
+ ]
+ const router = new VueRouter({
+ routes,
+ })
+ export default router;
main.ts
:
ts
import 'vue-class-component/hooks'
import Vue from 'vue'
import App from './App.vue'
+ import router from './router'
Vue.config.productionTip = false
new Vue({
+ router,
render: h => h(App),
}).$mount('#app')
上面简单配置了两个路由组件Home
跟Center
,Home
用来实现生命周期钩子里获取数据,Center
实现路由守卫里获取数据。
路由组件
生命周期中获取数据
Home
:
html
<template>
<div>
<Loading v-if="isLoading" />
<div v-else>
homeData: {{ homeData }}
</div>
</div>
</template>
<script lang="ts">
import Loading from '@/components/Loading.vue';
import { Component, Vue } from 'vue-property-decorator';
import { getHomeData } from '../api/index';
@Component({
components: {
Loading
}
})
export default class Home extends Vue {
public isLoading = true;
public homeData = '';
async created() {
try {
const homeData = await getHomeData();
this.homeData = homeData;
this.isLoading = false;
} catch(e) {
console.log(e);
}
}
mounted() {
console.log(this);
}
}
</script>
<style scoped>
</style>
getHomeData
即用定时器简单模拟了一下网络请求,@/api/index.ts
:
ts
export const getHomeData: () => Promise<string> = async () => {
// 模拟ajax请求
const p = new Promise<string>((resolve) => {
setTimeout(() => {
resolve('home data');
}, 3000);
})
return p;
}
export const getCenterData: () => Promise<string> = async () => {
const p = new Promise<string>((resolve) => {
setTimeout(() => {
resolve('center data');
}, 3000);
})
return p;
}
自定义的Loading
组件也非常简单,没有样式,重在理清逻辑,点到为止~,@/components/Loading.vue
:
html
<template>
<div>
loading...
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class Loading extends Vue {
}
</script>
路由守卫中获取异步数据
因为使用class-component
的缘故,我们需要事先注册一下vue-router的钩子class组件才能识别router的路由守卫钩子
注册router-hook
@/class-component-hooks.ts
:
ts
// class-component-hooks.js
import Component from 'vue-class-component'
// Register the router hooks with their names
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
'beforeRouteUpdate'
])
在项目入口文件中引用一下,但一定要保证引用语句在导入组件的语句之前,如import App from './App.vue'
之前,@/main.ts
:
ts
+ import './class-component-hooks' // 先于组件引用
import App from './App.vue'
new Vue({
router,
render: h => h(App),
}).$mount('#app')
因为自己的Loading
太丑了,所以还是用Element-ui
的组件吧
引入element-ui
安装依赖:pnpm i element-ui
全局引入组件库的样式,main.ts
:
arduino
import 'element-ui/lib/theme-chalk/index.css';
这样在需要Loading
组件的地方导入并使用就可以了
Center.vue
路由组件
下面是Center
组件的逻辑,@/pages/Center.vue
:
html
<template>
<div>
<div>
centerData: {{ centerData }}
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { getCenterData } from '../api/index';
import { Route } from 'vue-router';
import { Loading } from 'element-ui';
@Component
export default class Center extends Vue {
public centerData = '';
async beforeRouteEnter(to: Route, from: Route, next: (callback: (vm: Center) => void) => void) {
try {
const loading = Loading.service({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const centerData = await getCenterData();
next(vm => vm.setCenterData(centerData));
// next后的函数体仍会执行
loading.close();
} catch(e) {
console.log(e);
}
}
setCenterData(centerData: string) {
this.centerData = centerData;
}
}
</script>
<style scoped>
</style>
说白了就是beforeRouteEnter
这个钩子里面先给一个loading
的反馈,数据获取之后next
放行以及关闭loading,但注意给next
方法标注类型的时候,next
函数回调的入参vm
标注当前组件类型Center
比较合适,方便后续调用setCenterData
方法来修改组件内数据,之所以要通过这种方式修改组件数据,因为beforeRouteEnter
钩子执行时机是早于beforeCreate
(组件创建)的,所以vue-router考虑到了用户操作组件状态的需求,所以暴露给我们一个异步回调,回调中可以访问组件实例用来后续组件操作。
测试与结果
简单在HelloWorld.vue
中消费一下这俩路由组件,@/components/HelloWorld.vue
:
html
<template>
<div class="hello">
<router-view></router-view>
<router-link to="/home">go Home</router-link>
<hr />
<router-link to="/center">go Center</router-link>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
}
</script>
效果: