如果在簡歷上寫“XX電商系統”的實現,其實第一直覺是這個人一定是從培訓班出來的。而我們“專案管理”課程正好就是做一個小型電商網站。開發時長一個月左右,包含買家端、賣家端、管理員端,雖然業務邏輯比較常見,但是這次開發仍有收穫,最重要的一點收穫就是 對Vuex有了真正的實踐和認識。 所以,本文大部分介紹Vuex在該專案上的實踐以及所踩的坑,另外的部分則是專案中一些其他要點的總結。
過去那臃腫的Vue元件
前面的幾次專案開發,拿到後端介面和需求之後,會按照以下的方式去實現功能:
- 劃分出 Components 和 Pages,儘量保證一些元件能夠複用。
- 抽象出部分可以複用的 SCSS 程式碼以及設計通用的 style class。
- 基於某個 HTTP 庫(如 axios)配置並封裝一層底層方法,並基於該方法之上書寫具體的API 函式。
- 在頁面和元件內呼叫上述封裝的方法,在函式回撥或
Promise
的then
方法裡進行資料更新或者做更多的業務處理。
好像沒什麼問題哈?
第1、2、3步基本上是沒太大問題的,問題就出在第4步,其結果是導致一個Vue檔案越來越大,到後面一個複雜的頁面vue檔案將會包含如下內容:
data
裡大量的資料欄位,有控制是否展示元素的
,渲染用資料
,用於"上鎖"的變數
等。- 在
created
或mounted
裡包含了大量的請求
和回撥處理
。 - 在
template
裡使用的元件的屬性內,繫結了大量的props
。
最後的結果就是vue檔案變得臃腫。
檔案越長,可讀難度會提高,可維護性會降低。
誰也不喜歡看一個包含了邏輯處理、介面處理、資料處理的冗雜程式碼檔案。
所以,為了解決上述問題,引入 Vuex
是非常必要的。
其實,之前也一直在使用Vuex,不過用的目的僅僅是為了
“儲存一點資料能夠全域性使用”
而已,根本沒有深入使用Action
,Getters
,Mutations
等。 本文關於Vuex不會過多介紹其原理以及好處,主要關注實踐以及使用心得,Vuex介紹或入門請移步官方文件。
乾淨、解耦合、職責分明
筆者所做的,是將關於資料請求和更新,基本都放到了Vuex
裡,讓Vuex
成為了整個專案的“資料集散中心”
,而vue
元件裡,僅僅是進行事件排程(dispatch)
和繫結來自Vuex的資料
。
簡單的例子
我們以商品詳情頁面舉例,其包含如下的功能:
- 第一次進入頁面根據URL引數中的id請求商品資料。
- 根據商品的屬性選擇更新庫存和價格。
- 新增購物車並更新小紅點。
- 下單。
- 收藏商品。
如圖所示:
如果我們不使用Vuex,程式碼的結構可能如下:
<template>
<template>
<script>
export default {
data() {
detail: {
name: '',
shopId: '',
id: '',
pic: '',
description: '',
price: '',
updateTime: '',
categoryId: '',
stock: 0,
createTime: '',
attributeList: {},
collectId: 0,
},
// 一大堆商品的欄位資料 【MARK】
},
created(){
this.$http.get('/product/:id')
.then()
// 以及一端更新其他資料的請求 【MARK】
},
methods(){
clickCart(){
this.$http.get('xxx')
.then('xxx')
this.$emit('xxx'); // 與上層元件通訊更新購物車的紅點
}
// 一堆使用者事件的觸發方法,每個方法內都是一堆http請求和回撥處理 【MARK】
},
computed:{
// 一些基於商品資料判斷狀態的資料 【MARK】
}
}
</script>
<style>
<!-- 忽略相關樣式程式碼-->
<style>
複製程式碼
如果按照上述的方式來寫,雖然可以保證關於這個頁面的邏輯處理都保留在這個vue檔案內,但是卻顯得十分臃腫,如果後續要加更多的功能抑或是修改,其需要讀的程式碼可能更多。
所以,以上程式碼示例中的被【MARK】
標記的部分,將是Vuex
可以優化的。
最後的程式碼如下圖所示:
程式碼的可讀性提高了不少:
- 該檔案裡看不到任何的http請求,也看不到任何請求路徑。(請求路徑一般是靜態的,直接寫HTTP請求路徑相當於寫死了資料的來源,對於後期的更改資料來源非常不友好)
data
裡不含任何業務資料欄位,僅保留控制頁面的欄位- 程式碼結構變得簡單,只有
【觸發事件】-【回撥】
這樣簡單的邏輯。
關於資料處理的程式碼,全部封裝到了Vuex
上了:
我們在Vuex層,不僅請求了資料,同時對返回的資料做了判斷以及不同的邏輯處理,同時設定Getters
使的頁面能夠基於Getters
提供的資料做各類二元判斷
。
Vuex層帶來了如下的好處:
- 在不同的頁面可以觸發相同的
Actions
,就這個例子,我只要寫了一次加入購物車
的函式,在各個頁面都可以使用了。 - Actions之間可以組合使用,通過組合的方式,我們可以更加輕易的實現複雜的業務邏輯,比如
新增商品進購物車的同時更新商品資料並更新購物車資料
,這樣需要寫三次HTTP
請求的業務邏輯,此時只需要將三個Actions進行組合即可,而這幾個Actions還可以在其他地方單獨使用。 - 直觀,一眼看過去,就知道商品模型上有哪些動作,對於 New Guy 熟悉業務邏輯更加方便,也方便開發者檢查自己的程式碼或者函式是否考慮周全。(BTW,在一堆HTML裡找邏輯是真的難受)
- 減少元件間通訊次數,一箇中心化的
資料倉儲
帶來的好處就是減少了資料層層傳遞的操作,按照以前的編碼習慣,我會大量使用$emit
和props
來實現父子或兄弟元件通訊,而現在,只需要在元件中觸發相應的事件繫結相應的資料即可。
全新的業務編寫體驗
直觀體驗來說,徹底使用Vuex
後,編碼思路清晰不少,因為寫完整體的一個功能需要2~3天,而Vuex對於“間斷性程式設計”
非常有幫助,能夠迅速撿起上一次開發的進度。
使用Vuex之後就是按照如下的步驟進行開發了:
(第1、2、3步不變,如前文)
4.構建Vuex的Store模型,定義所需的方法。
5.編寫元件並繫結在Vuex上的相關資料和方法。
6.編寫觸發dispatch
後的回撥。
筆者認為使用Vuex幾個重要的原則
使用Vuex
存在一定的規範,在官網上已經陳列了諸如表單處理
,測試
,專案結構
的規範,而下面的一些原則僅僅是筆者的一些經驗之談,不一定正確,有所幫助便是最好的。
【不處理頁面跳轉】:頁面跳轉在dispatch
的回撥裡進行處理。
最初我的設計是在Action
請求資料之後同時也負責所有的跳轉邏輯
和彈窗邏輯
,不過,這樣做又讓檢視層
和資料層
耦合了,使得我抽象出來的Action
的複用性變得極差,同時,在vue
檔案看到關於檢視層的處理才算是比較正常的編寫邏輯。
【單一職責原則】:mutation
和action
儘量保持只做一件事原則。
正如上一條原則中提到的要提高action
的複用性,那麼就要保證需要複用的action
只做一個功能,如更新A資料
和更新B資料
不要直接融合成一個action
,而是分別定義兩個action
,並通過組合的方式來實現一個更復雜的功能,有點類似精簡指令集的思想。
如:
actions:{
updateA(){}, // A 可能在其他地方被單獨使用
updateB(){}, // B 可能在其他地方被單獨使用
updateAandB({dispatch}){
dispatch("updateA");
dispatch("updateB");
}, // 同時觸發 A B 的複雜action
}
複製程式碼
【使用Promise和AA】(Async & Await)
為了讓dispatch
函式擁有Promise風格
的處理回撥的能力,可以讓action
的返回值作為一個Promise
物件,如:
// store.js
actions:{
updateA(){
return Promise.resolve()
}
}
複製程式碼
在元件內就可以使用.then
:
...
created(){
this.$store.dispatch('updateA')
.then(() => {
...
})
}
...
複製程式碼
而Vuex
一般建議在actions
進行非同步操作,所以為了讓程式碼更加優雅,可以用AA
如下編寫:
// store.js
actions:{
async updateA(){
const result = await ajax('/**/**');
if(result){
return Promise.resolve()
}
}
}
複製程式碼
所以,在實踐中的程式碼如圖:
【使用namespace】,為了你自己好。
一般使用vuex,會根據實體或者頁面來拆分state形成module,一個module裡包含其所需的所有actions,states,mutation
等。如電商裡,一般有商品
,訂單
,使用者
這幾個實體可以拆成module,拆分出來之後如圖:
拆分的好處是更加獨立直觀了,壞處是如果不使用名稱空間,可能造成actions
,mutations
衝突,正好Vuex
也提供namespace
的配置,參考官方文件即可。
這次使用的不足
未使用namespace
雖然總結到了原則裡,不過這次並沒有使用namespace
,算是這次比較大的不足了,解決的辦法是”手動namespace“,如圖:
將actions
的方法名字首加上該實體名用於區分不同的方法,實在是比較拙劣的手段了。
部分程式碼仍然有重複書寫的情況
情況發生在獲取到非同步資料之後的程式碼,一般如下這段程式碼會大量重複:
if (result.code){
return Promise.resolve(data);
}
return Promise.reject(msg);
複製程式碼
雖然不算多,但是這邏輯仍然有可抽象出來的可能性,下次打算在RootState
裡新增一個action
,用於處理這樣的後端返回資料,期望結果如下:
actions: {
parseResult({}, result){
if (result.code){
return Promise.resolve(data);
}
return Promise.reject(msg);
},
async getA({dispatch}){
const result = await http.get('xx/xx');
return dispatch('parseResult', result); // One line , it's clean
}
}
複製程式碼
其他收穫
擁抱 Vue CLI 3
3.0 基本在配置上做到極簡化
了。很久沒開發Vue
的專案了,記得上一個Vue
專案裡,仍然有一大堆xxx.congfig.js
,而現在,基本整合到了CLI內部了,通過一個vue.config.js
可滿足大部分開發需求。本專案配置如下圖:
比以前的配置更加直觀了,更多請參考:cli.vuejs.org/zh/,至於外掛機制,本次開發未用上,只是在安裝第三方包的時候可以用vue add
了,有GUI可以檢視安裝了哪些外掛。
將Vue.use 和 Vue.install
使用的地方彙總到plugins
目錄下
本專案是一個多頁面應用,總共有3個Vue例項,所以希望能夠按需注入想要的第三方依賴如axios
,qrcode
,lazyload
等一些工具庫或第三方元件。於是都將其彙總到plugins
目錄下了,如下圖:
這樣封裝的好處是,我可以對每個Vue例項按需匯入第三方依賴,一行import就可以,如下圖:
開始考慮優化了
一個大型的web app 打包壓縮後都不超過500kb,所以就以此作為Benchmark
來優化該專案打包後的大小,按需引入
第三方依賴(特別是Element
和lodash
)減少了不少專案體積。不過最主要的還是得學會用webpack analyse tool
來分析。
結論
總之,本次專案主要是重新整理了對Vuex
的認知,其能夠讓我們編寫的vue
元件可讀性和可維護性更高。同時,也能夠讓資料更新這塊的程式碼複用性更高。通過Vuex
來構建一個中心化的資料集散中心
還能讓開發的思路更順暢,值得學習。
參考: