在開始本篇的主題之前,讓我們把上次遺留下來的問題都清理一下:
- 將其他元件中
axios
請求的地方封裝起來。
這裡就不把程式碼放在開頭了,相關程式碼都放在文末,有興趣瞭解的童鞋可以先往下翻。
好了, 我們現在把上篇剩下的任務給完成了,接下來我們來正式開始本篇內容吧。
去重是什麼
字面上意思:去除重複,在專案中,不可避免的會出現重複程式碼。但是如果不好好去處理這些重複程式碼,那很有可能就會給你很多“驚喜”。
如何為“重複” 下一個定義呢?
從最淺顯的層次來看, 相同即是重複。在我們上面的程式碼中,每一個元件中都有這麼一行程式碼:
import RequestSender from '@/requestSender';
這就是重複程式碼,在每一個需要發起請求的元件中你都會需要寫上這麼一行程式碼。那麼讓我們就這個列舉一些可能出現的問題:
某一天修改了檔名
某一天移動了該檔案
那麼專案中需要修改的地方將會是多少呢?而在修改中會產生手誤的概率又會是多少呢?以上還是在單人開發的時候,如果團隊協作開發,這些情況的概率又會是什麼樣的呢。
如何去重
當然,對於上面這種引入型的程式碼,類似移動檔案,修改檔名這種操作。IDE 就能很好的幫你處理,比如 WebStorm
如果你使用重構相關的功能去重新命名,那麼它會找出所有 “疑似”引用的程式碼片段,你可以選擇所有相關的引用同時修改。
這是一種手段,很好的解決了上面這些問題。
那麼讓我們來看看另一個重複程式碼的問題:
class RequestSender {
static GetBlogList() {
return axios.get('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/list');
}
static Publish(data) {
return axios.post('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/publish', data);
}
static Login(data) {
return axios.post('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/login', data);
}
static Signup(data) {
return axios.post('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/signup', data);
}
}
上面的程式碼, 重點不在函式噢。 仔細看看它們有哪些地方重複了。
光從程式碼上來看,其實有很多“重複”的地方,比如說 return
、static
、axios.get
、axios.post
。
這些重複有一部分是語法,有一部分是呼叫。這些都是不可避免的,因此這些重複程式碼並不在我們需要重構的範圍內。那麼,究竟是哪段程式碼呢?
https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io
準確來說,它並不算是程式碼。而是“硬編碼”,從整體程式碼上來看,這是目前所有後臺介面的域名
。
在開發過程中,一般來說至少是會有兩個環境存在:開發環境、線上環境。而它們兩的後臺介面域名一般而言又不會重複,難道每次釋出前都手動改一下域名麼?
我們先來列舉一下可能會出現的問題:
開發環境、線上環境域名不一致
團隊協作中,開發者之間的開發域名不一致
當線上/開發 環境中的域名需要修改時
可以看到,當遇到上述問題時,專案中所有硬編碼了域名
的地方都是需要修改的,那麼為什麼要修改呢?
除了解決上面列舉的具體問題之外,最根本的目的是:
- 保持唯一性
如果有兩段/多段程式碼它們表示的含義完全一致,並且從目的上來說也是一致的。那麼就應該儘可能將其只保留一處定義。
那麼對於這個域名我們怎麼處理呢?首先將其提煉出來:
static Host = 'https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io';
這樣,引用的地方就可以這麼寫:
static GetBlogList() {
return axios.get(`${Host}/list`);
}
這樣,當發現修改的時候,是不是隻需要修改 Host
這麼一個地方就好了呢?、
但是這樣還存在問題,如果要釋出,或者是在 git
、 svn
上協作的時候呢? 每個人、每個環境都需要修改這個變數,並且還要在提交程式碼時移除掉自己的修改以避免衝突。
可配置化
Host
的例子是非常常見的,當我們需要釋出、團隊協作的時候,環境不同是非常常見的,有可能在自己電腦上 Host
是 localhost:8080
,換在另一個人電腦上就是 localhost:9099
了。那麼線上環境有可能又是 xxx.xxx.com
、xx.xxx.com/api
諸如此類。
這裡若羽實踐的解決方案是:
將與環境相關的硬編碼提煉成可配置項放入配置檔案
配置檔案模板化
配置模板檔案多樣化
真正的配置檔案是不會被提交上去,只有一個模板檔案。由於配置檔案並不會被提交,所以開發者之間的環境差異就可以忽略了,大家根據自己的環境修改配置檔案即可。
那麼對於線上環境、測試環境等等,建立對應的配置檔案模板即可。當釋出時,使用對應環境的釋出配置檔案模板作為配置檔案即可。
那麼我們來實踐一下:
新建配置模板檔案 config.js.template
:
const config = {
HOST: '',
};
export default config;
接下來複制貼上模板檔案,並重新命名為 config.js
:
const config = {
HOST: 'https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io',
};
export default config;
接下來修改一下 requestSender.js
:
import axios from 'axios';
import config from '@/config.js';
class RequestSender {
static GetBlogList() {
return axios.get(`${config.HOST}/list`);
}
static Publish(data) {
return axios.post(`${config.HOST}/publish`, data);
}
static Login(data) {
return axios.post(`${config.HOST}/login`, data);
}
static Signup(data) {
return axios.post(`${config.HOST}/signup`, data);
}
}
export default RequestSender;
好了,現在不管是在任何一個環境下,都可以遊刃有餘的切換域名了。而且這裡面還有一個很有意思的事情:
- 所有的改動對於表現層而言是透明的。
簡單來說,我們在這裡重構了這麼多的程式碼,然而我們並不需要修改任何一個檢視元件中的程式碼!!!
表面上還是原來的樣子,可實際上已經“打掃”過了。這也是重構中需要注意的一點:
- 步子邁小一點,邁準一點
寫在後面
上篇中有人問到若羽說封裝請求的意義何在,axios
本身就是帶著 Promise
的支援了。
這裡對這個問題做一個迴應,立場僅代表若羽本人,並不為任何人“做代表”:
封裝並非為了
Promise
,而是為了將“傳送請求”的這個動作封裝起來。因為這屬於資料獲取的行為,而後面then
裡的邏輯實際上是和業務掛鉤:為檢視設定資料。這是兩個不同的行為,就像後端一樣:ORM
它僅僅是負責從資料庫中取資料而已,真正對這個資料進行邏輯操作的,並不是它。這也是接下來博文的主題:專一,一個函式應當只負責一件事情。這一篇文章便表示了另一層意思:去重,在第一層封裝的過程中我們發現了
域名
的硬編碼問題(不封裝也是一樣),因此在這裡如果不做封裝的話,即使將域名提煉出來,涉及到修改的檔案同樣也會較多。不過這種修改是一次性的。
以上便是若羽對上一篇中示例的解釋。
歡迎大家發表評論,共同探討。
上篇重構程式碼
edit.vue
:
<script>
import RequestSender from '@/requestSender'
export default {
name: "Edit",
data() {
return {
model: {
title: '',
content: '',
}
}
},
methods: {
submit() {
RequestSender.Publish(this.model)
.then(res => {
if(res.data.Code === 200) {
this.$message.success('釋出成功');
}
})
}
}
}
</script>
Login.vue
:
<script>
import RequestSender from '@/requestSender';
export default {
name: "Login",
data() {
return {
model: {
username: '',
password: '',
}
}
},
methods: {
submit() {
RequestSender.Login(this.model)
.then(res => {
if(res.data.Code === 200) {
this.$message.success('登入成功');
}
})
}
}
}
</script>
Signup.vue
:
<script>
import RequestSender from '@/requestSender';
export default {
name: "Signup",
data() {
return {
model: {
username: '',
password: '',
rePassword: ''
}
};
},
methods: {
submit() {
if(this.model.password !== this.model.rePassword){
this.$message.error('兩次出入密碼不一致.');
return ;
}
RequestSender.Signup(this.model)
.then(res => {
if(res.data.Code === 200){
this.$message.success("註冊成功");
this.$router.push('./login');
}
});
}
}
}
</script>
requestSender.js
:
import axios from 'axios';
class RequestSender {
static GetBlogList() {
return axios.get('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/list');
}
static Publish(data) {
return axios.post('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/publish', data);
}
static Login(data) {
return axios.post('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/login', data);
}
static Signup(data) {
return axios.post('https://451ece6c-f618-436b-b4a2-517c6b2da400.mock.pstmn.io/signup', data);
}
}
export default RequestSender;