# 重構代碼
[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 小結