接上篇《【Geek议题】合理的VueSPA架构讨论(上)》传送门。javascript
登陆状态标识符跟token相似,都是须要自动维护有效期,但也有些许不一样,获取过程只在用户登陆或注册的时候,不须要自动获取。
本人比较推荐使用公共状态管理vuex进行自动化管理,并配合路由钩子,减小代码编写时的顾虑。vue
示例中公共状态管理中的user模块里定义了userIdObj,其中包含了userId登陆状态标识符和过时时间。
维护userId是否过时主要是经过vuex中的getter来实现。java
const getters = { getUserId: (_state) => { // 获取公共状态中的userIdObj const userIdObj = { ..._state.userIdObj, }; // 是否过时标识 let isExpire = false; // 判断是否过时 if (userIdObj && userIdObj.userId) { isExpire = new Date().getTime() - userIdObj.expireTime > -10000; } // 若是过时则返回空字符串(不必定) if (!userIdObj || !userIdObj.userId || isExpire) { return ''; } // 没过时则返回userId return userIdObj.userId; }, };
回顾上篇中全局的路由钩子router.beforeEach
,当目标路由元信息requiresAuth为true则表示,这个路由必须有登陆状态才能访问,这时候就会进行登陆状态检查。处理思路以下:ios
【PS】示例这里的处理还不完美,最好跳转登陆前保存好目标路由,登陆成功就直接跳转去该路由。
router.beforeEach((to, from, next) => { // ... // 检查登陆状态 if (to.meta.requiresAuth) { console.log('目标路由须要登陆状态'); if (!store.getters.getUserId) { console.log('内存无登陆信息,尝试在本地存储中找'); const localUserIdObj = JSON.parse(localStorage.getItem('userIdObj')); if (localUserIdObj) { // 若是本地存储中有userIdObj,则提交到公共状态 store.commit('setUserIdObj', localUserIdObj); } } // 再次检查公共状态里有没有userId if (!store.getters.getUserId) { console.log('依旧无登陆信息'); router.push({ name: 'userLogin', }); } } next(); });
在“页面”中获取userId也很简单,使用计算属性是最好的,返回的userId具备响应性,这作的好处也是为了实时将登陆状态反应到页面上,才不会出现显示已登陆,但用户刷新一下才能知道登陆状态已过时的尴尬状况。vuex
【PS】一些须要用户登陆状态的api函数,也是经过这样的方法获取并使用。固然,建议在非首屏加载使用的api函数(好比提交表单),须要在调用前,检查一下userId还存不存在,以避免出错。
computed: { // 获取登陆状态 userId() { return this.$store.getters.getUserId; }, },
有时会有须要取消请求的需求,好比上传文件耗时过长,用户不想等,这时候就必须有取消的功能。
上篇提到的axios已经提供了一个基于CancelToken的取消机制,这里咱们要配合vuex实现对全局链接的实时监控。axios
【PS】示例的接口名称都是在写api函数的时候定义好的,想要更加灵活,能够每次请求都单独指定名字。
示例中给公共状态下的com模块添加了cancelToken数组,用来保存发起的链接的名称和source(取消标记),并提供了以下三个mutations方法segmentfault
// 增长cancelToken addCancelToken(_state, cancelToken) { _state.cancelToken.push(cancelToken); const arr = _state.cancelToken; _state.cancelToken = arr; }, // 删除指定名字的cancelToken deleteCancelToken(_state, name) { _state.cancelToken.some((i, index) => { if (i.name === name) { _state.cancelToken.splice(index, 1); return true; } return false; }); }, // 清空cancelToken clearCancelToken(_state) { _state.cancelToken.forEach((i) => { if (i.source && typeof i.source.cancel === 'function') { i.source.cancel(`cancel${name}`); } }); _state.cancelToken = []; },
基本思路:api
// 请求发起前拦截器 myAxios.interceptors.request.use((_config) => { const config = _config; const source = axios.CancelToken.source(); // 获取cancelToken config.cancelToken = source.token; // 取消请求标记保存 store.commit('addCancelToken', { name: config.name, source, }); return config; }, () => { // 异常处理 console.error('请求发起前拦截器异常'); }); // 响应拦截器 myAxios.interceptors.response.use((response) => { // 删除取消标记 store.commit('deleteCancelToken', response.config.name); console.log(response); return response; }, (error) => { console.error(error); // 清理取消标记 store.commit('clearCancelToken'); return Promise.reject(error); });
使用计算属性获取cancelToken数组,这里是响应式的,全部咱们其实能够知道如今有多少个请求还未完成了。数组
【PS】实时对全局请求的监控已经实现,其实能够作的扩展就不少了,好比能够全局化loading的显示,只要数组内一直不为空持续一段时间就能够判断须要显示loading,若是为空则关闭loading。
computed: { // 获取链接列表 cancelToken() { return this.$store.state.com.cancelToken; }, },
遍历这个数组,查找到对应名字的链接就能够取消了。架构
this.cancelToken.forEach((i) => { console.log(i); if (i.name === '上传文件' && typeof i.source.cancel === 'function') { console.log('取消上传'); i.source.cancel(`cancel${name}`); } });
在初始化Vue的根组件前,给Vue的原型链上添加经常使用的工具,能够方便在vue文件中使用。这样作会影响全部Vue示例推荐只在单页面应用中使用。
好比下面以咱们的api集为例,这样在vue文件中this.$api
就可使用咱们的api集,不须要重复引用。
// 将api模块挂载进vue方便在this调用 Vue.prototype.$api = api;
在util文件夹下能够新建一个专门用来存过滤器的filter.js,而后批量导入的全局过滤器中。
import * as filters from './util/filter'; Object.keys(filters).forEach(k => Vue.filter(k, filters[k]));