# 重构代码
[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 被持久化存储和调用。
- 第一章. 基础信息
- 1.1 序言
- 1.2 关于作者
- 1.3 本书源码
- 1.4 问题反馈
- 第二章. 舞台布置
- 2.1 开发环境搭建
- 2.2 产品分析
- 2.3 创建后端应用
- 2.4 创建前端应用
- 第三章. 构建页面
- 3.1 章节说明
- 3.2 第一个 API
- 3.3 静态页面
- 3.4 Think 命令
- 3.5 小结
- 第四章. 优化页面
- 4.1 章节说明
- 4.2 使用路由
- 4.3 注册页面
- 4.4 样式美化
- 4.5 小结
- 第五章. 用户模型
- 5.1 章节说明
- 5.2 数据库迁移
- 5.3 模型
- 5.4 小结
- 第六章. 用户注册
- 6.1 章节说明
- 6.2 接收数据
- 6.3 数据验证
- 6.4 写入数据
- 6.5 前端页面
- 6.6 小结
- 第七章. 会话管理
- 7.1 章节说明
- 7.2 会话控制
- 7.3 前端拦截
- 7.4 使用 Vuex
- 7.5 用户登入
- 7.6 用户登出
- 7.7 小结
- 第八章. 用户数据
- 8.1 章节说明
- 8.2 查找用户
- 8.3 重构代码
- 8.4 错误处理
- 8.5 个人资料
- 8.6 更新资料
- 8.7 小结
- 第九章. 推文数据
- 9.1 章节说明
- 9.2 推文模型
- 9.3 发送推文
- 9.4 发送推文前端页面
- 9.5 推文流
- 9.6 用户的所有推文
- 9.7 小结
- 第十章. 用户关系
- 10.1 章节说明
- 10.2 粉丝模型
- 10.3 关注与取消关注
- 10.4 已关注用户的推文
- 10.5 小结