# 重构代码 [TOC] 我们在之前的章节中提到过: 由于 Vuex 只会存在于一次会话中,刷新页面就没有了,而 localStorage 在浏览器端是持久存储的,所以我们还需要 * 封装 Axios * 检测 token 可用性 * 初始化页面时使用该 token 请求用户数据 * 将用户数据塞入 Vuex 并且我们编写了太多重复代码,这样不利于维护和使用,所以本节的主要目标就是优化所有代码,并且解决已知问题。 ## 检测 token 可用性 我们需要在入口文件初始化的时候检测一次可用性: src\App.vue ```html <script> export default { data: () => ({ navItems: [{ title: "主页", icon: "mdi-home", to: "/" }], navUserItems: [ { title: "注册", icon: "mdi-account-plus", to: "/sign" }, { title: "登录", icon: "mdi-login", to: "/login" } ] }), mounted() { const config = { token: localStorage.getItem("JWT_TOKEN"), api: "http://127.0.0.1:8000/", postData: "", headers: { Authorization: "Bearer " + config.token } }; this.login(config, 'me'); }, methods: { login(config, url) { if (config.token) { this.axios .post( config.api + url, config.postData, config.headers ) .then(response => { const data = response.data; this.$store.commit("user_data", data); this.$store.commit("login"); this.$router.push("/"); }) .catch(() => { this.logout(); }); } else { this.logout(); } }, logout() { const token = localStorage.getItem("JWT_TOKEN"); const postData = ""; const api = "http://127.0.0.1:8000/logout"; const headers = { Authorization: "Bearer " + token }; this.axios .post(api, postData, { headers: headers }) .then(() => { this.$store.commit("logout"); }) .catch(error => { alert(error.response.data.error); }); } } }; </script> ``` ## 封装 Axios 虽然可以正常发送请求的,但是编写过于繁琐,可以看到我们 HTTP 请求的代码太过于重复,所以我们需要将它抽象出来。 创建文件 model\http.js: ```javascript title="model\http.js" /** * axios封装 * 请求拦截、响应拦截、错误统一处理 */ import axios from 'axios'; import router from '../src/router/index'; import store from '../src/store/index'; /** * 跳转登录页 * 携带当前页面路由,以期在登录页面完成登录后返回当前页面 */ const toLogin = () => { router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }); } /** * 请求失败后的错误统一处理 * @param {Number} status 请求失败的状态码 */ const errorHandle = (status, other) => { // 状态码判断 switch (status) { case 500: case 401: store.commit('logout'); toLogin(); break; // 404请求不存在 case 404: console.log('请求的资源不存在') break; default: console.log(other); } } // 创建axios实例 const instance = axios.create({ timeout: 1000 * 12 }); // 设置post请求头 instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; /** * 请求拦截器 * 每次请求前,如果存在token则在请求头中携带token */ instance.interceptors.request.use( config => { // 登录流程控制中,根据本地是否存在token判断用户的登录情况 // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码 // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。 const token = localStorage.getItem("JWT_TOKEN"); config.headers.Authorization = 'Bearer ' + token return config; }, error => Promise.error(error)) // 响应拦截器 instance.interceptors.response.use( // 请求成功 res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res), // 请求失败 error => { const { response } = error; if (response) { // 请求已发出,但是不在2xx的范围 errorHandle(response.status, response.data.message); return Promise.reject(response); } else { // 处理断网的情况 // eg:请求超时或断网时,更新state的network状态 // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏 // 关于断网组件中的刷新重新获取数据,会在断网组件中说明 if (!window.navigator.onLine) { store.commit('changeNetwork', false); } else { return Promise.reject(error); } } }); export default instance; ``` 该文件是我们初始化 Axios 时设置的一些内容,包括请求头,错误处理等等。 现在我们还需要创建一个 API 模块来集中管理: model\api.js ```javascript title="model\api.js" /** * api接口统一管理 */ import axios from './http' import store from '../src/store/index'; import router from '../src/router/index'; const domain = 'http://127.0.0.1:8000' const api = { userDetail(id) { return axios.get(`${domain}/user/${id}`); }, me() { return axios .post(`${domain}/me`) .then((res) => { store.commit("user_data", res.data); store.commit("login"); }); }, auth(action, params) { return axios .post(`${domain}/${action}`, params) .then((res) => { localStorage.setItem("JWT_TOKEN", res.data.token); store.commit("user_data", res.data); store.commit("login"); router.push("/"); }); }, logout(params) { return axios .post(`${domain}/logout`, params) .then(() => { localStorage.removeItem('JWT_TOKEN'); store.commit('logout') router.push("/login"); }); } } export default api; ``` 可以看到,我们在该文件内编写了几个之前用到的获取数据的方法,这样在前端再也不用挨着挨着重复的书写了。 接着更新一下 Vuex 文件: src\store\index.js ```javascript title="src\store\index.js" import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { user: { isLogin: false, data: "" } }, mutations: { user_data(state, data) { state.user.data = data; }, login(state) { state.user.isLogin = true; }, logout(state) { state.user.isLogin = false; state.user.data = ""; } }, actions: {}, modules: {} }); ``` 好了,封装 Axios 基本已经完成了,我们现在所需要做的就是在前端去调用它: src\App.vue ```html title="src\App.vue" <script> export default { data: () => ({ navItems: [{ title: "主页", icon: "mdi-home", to: "/" }], navUserItems: [ { title: "注册", icon: "mdi-account-plus", to: "/sign" }, { title: "登录", icon: "mdi-login", to: "/login" } ] }), mounted() { this.$api.me() }, methods: { } }; </script> ``` src\views\Login.vue ```html title="src\views\Login.vue" <script> export default { data: () => ({ email: "", password: "", error: "" }), methods: { login() { const params = { email: this.email, password: this.password }; this.$api.auth('login', params); } } }; </script> ``` src\views\Sign.vue ```html title="src\views\Sign.vue" <script> export default { data: () => ({ email: "", password: "", error: "" }), methods: { sign() { const params = { email: this.email, password: this.password }; this.$api.auth('sign', params); } } }; </script> ``` 接着我们还需要在入口处注册 api: src\main.js ```javascript title="src\main.js" import Vue from "vue"; import App from "./App.vue"; import "./registerServiceWorker"; import router from "./router"; import store from "./store"; import vuetify from "./plugins/vuetify"; import api from '../model/api' Vue.prototype.$api = api; // 将api挂载到vue的原型上 new Vue({ router, store, vuetify, render: h => h(App) }).$mount("#app"); ``` Vue.prototype 是 Vue 原型方法,例如上面的代码就是将 api 挂载到 Vue 上,那么在文件中我们就可以通过 `this.$api` 进行调用。 ## 清理多余文件 删除: * src\views\About.vue * src\plugins\axios.js * src\components\HelloWorld.vue ## 移除多余扩展包 ```powershell title="Powershell" # 如果你使用的是 npm npm uninstall vue-axios # 如果你使用的是 yarn yarn remove vue-axios ``` 现在的问题基本都解决了,再次访问网站并刷新页面即可看到 token 被持久化存储和调用。