電商網站專案總結:Vuex 帶來全新的程式設計體驗

芒僧發表於2019-01-14

如果在簡歷上寫“XX電商系統”的實現,其實第一直覺是這個人一定是從培訓班出來的。而我們“專案管理”課程正好就是做一個小型電商網站。開發時長一個月左右,包含買家端、賣家端、管理員端,雖然業務邏輯比較常見,但是這次開發仍有收穫,最重要的一點收穫就是 對Vuex有了真正的實踐和認識。 所以,本文大部分介紹Vuex在該專案上的實踐以及所踩的坑,另外的部分則是專案中一些其他要點的總結。

過去那臃腫的Vue元件

前面的幾次專案開發,拿到後端介面和需求之後,會按照以下的方式去實現功能:

  1. 劃分出 Components 和 Pages,儘量保證一些元件能夠複用。
  2. 抽象出部分可以複用的 SCSS 程式碼以及設計通用的 style class。
  3. 基於某個 HTTP 庫(如 axios)配置並封裝一層底層方法,並基於該方法之上書寫具體的API 函式。
  4. 在頁面和元件內呼叫上述封裝的方法,在函式回撥或Promisethen方法裡進行資料更新或者做更多的業務處理。

好像沒什麼問題哈?

第1、2、3步基本上是沒太大問題的,問題就出在第4步,其結果是導致一個Vue檔案越來越大,到後面一個複雜的頁面vue檔案將會包含如下內容:

  • data裡大量的資料欄位,有控制是否展示元素的渲染用資料用於"上鎖"的變數等。
  • createdmounted裡包含了大量的請求回撥處理
  • template裡使用的元件的屬性內,繫結了大量的props

最後的結果就是vue檔案變得臃腫。

檔案越長,可讀難度會提高,可維護性會降低。

誰也不喜歡看一個包含了邏輯處理、介面處理、資料處理的冗雜程式碼檔案。

所以,為了解決上述問題,引入 Vuex 是非常必要的。

其實,之前也一直在使用Vuex,不過用的目的僅僅是為了“儲存一點資料能夠全域性使用”而已,根本沒有深入使用ActionGettersMutations等。 本文關於Vuex不會過多介紹其原理以及好處,主要關注實踐以及使用心得,Vuex介紹或入門請移步官方文件

乾淨、解耦合、職責分明

筆者所做的,是將關於資料請求和更新,基本都放到了Vuex裡,讓Vuex成為了整個專案的“資料集散中心”,而vue元件裡,僅僅是進行事件排程(dispatch)繫結來自Vuex的資料

簡單的例子

我們以商品詳情頁面舉例,其包含如下的功能:

  • 第一次進入頁面根據URL引數中的id請求商品資料。
  • 根據商品的屬性選擇更新庫存和價格。
  • 新增購物車並更新小紅點。
  • 下單。
  • 收藏商品。

如圖所示:

-w1270

如果我們不使用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可以優化的。 最後的程式碼如下圖所示:

電商網站專案總結:Vuex 帶來全新的程式設計體驗

程式碼的可讀性提高了不少:

  • 該檔案裡看不到任何的http請求,也看不到任何請求路徑。(請求路徑一般是靜態的,直接寫HTTP請求路徑相當於寫死了資料的來源,對於後期的更改資料來源非常不友好)
  • data裡不含任何業務資料欄位,僅保留控制頁面的欄位
  • 程式碼結構變得簡單,只有【觸發事件】-【回撥】這樣簡單的邏輯。

關於資料處理的程式碼,全部封裝到了Vuex上了:

電商網站專案總結:Vuex 帶來全新的程式設計體驗

我們在Vuex層,不僅請求了資料,同時對返回的資料做了判斷以及不同的邏輯處理,同時設定Getters使的頁面能夠基於Getters提供的資料做各類二元判斷

Vuex層帶來了如下的好處:

  • 在不同的頁面可以觸發相同的Actions,就這個例子,我只要寫了一次加入購物車的函式,在各個頁面都可以使用了。
  • Actions之間可以組合使用,通過組合的方式,我們可以更加輕易的實現複雜的業務邏輯,比如新增商品進購物車的同時更新商品資料並更新購物車資料,這樣需要寫三次HTTP請求的業務邏輯,此時只需要將三個Actions進行組合即可,而這幾個Actions還可以在其他地方單獨使用。
  • 直觀,一眼看過去,就知道商品模型上有哪些動作,對於 New Guy 熟悉業務邏輯更加方便,也方便開發者檢查自己的程式碼或者函式是否考慮周全。(BTW,在一堆HTML裡找邏輯是真的難受)
  • 減少元件間通訊次數,一箇中心化的資料倉儲帶來的好處就是減少了資料層層傳遞的操作,按照以前的編碼習慣,我會大量使用$emitprops來實現父子或兄弟元件通訊,而現在,只需要在元件中觸發相應的事件繫結相應的資料即可。

全新的業務編寫體驗

直觀體驗來說,徹底使用Vuex後,編碼思路清晰不少,因為寫完整體的一個功能需要2~3天,而Vuex對於“間斷性程式設計”非常有幫助,能夠迅速撿起上一次開發的進度。

使用Vuex之後就是按照如下的步驟進行開發了:

(第1、2、3步不變,如前文) 4.構建Vuex的Store模型,定義所需的方法。 5.編寫元件並繫結在Vuex上的相關資料和方法。 6.編寫觸發dispatch後的回撥。

筆者認為使用Vuex幾個重要的原則

使用Vuex存在一定的規範,在官網上已經陳列了諸如表單處理測試專案結構的規範,而下面的一些原則僅僅是筆者的一些經驗之談,不一定正確,有所幫助便是最好的。

【不處理頁面跳轉】:頁面跳轉在dispatch的回撥裡進行處理。

最初我的設計是在Action請求資料之後同時也負責所有的跳轉邏輯彈窗邏輯,不過,這樣做又讓檢視層資料層耦合了,使得我抽象出來的Action的複用性變得極差,同時,在vue檔案看到關於檢視層的處理才算是比較正常的編寫邏輯。

【單一職責原則】:mutationaction儘量保持只做一件事原則。

正如上一條原則中提到的要提高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()
        }
    }
}
複製程式碼

所以,在實踐中的程式碼如圖:

電商網站專案總結:Vuex 帶來全新的程式設計體驗

【使用namespace】,為了你自己好。

一般使用vuex,會根據實體或者頁面來拆分state形成module,一個module裡包含其所需的所有actions,states,mutation等。如電商裡,一般有商品訂單使用者這幾個實體可以拆成module,拆分出來之後如圖:

電商網站專案總結:Vuex 帶來全新的程式設計體驗

拆分的好處是更加獨立直觀了,壞處是如果不使用名稱空間,可能造成actions,mutations衝突,正好Vuex也提供namespace的配置,參考官方文件即可。

這次使用的不足

未使用namespace

雖然總結到了原則裡,不過這次並沒有使用namespace,算是這次比較大的不足了,解決的辦法是”手動namespace“,如圖:

電商網站專案總結:Vuex 帶來全新的程式設計體驗

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可滿足大部分開發需求。本專案配置如下圖:

電商網站專案總結:Vuex 帶來全新的程式設計體驗

比以前的配置更加直觀了,更多請參考:cli.vuejs.org/zh/,至於外掛機制,本次開發未用上,只是在安裝第三方包的時候可以用vue add了,有GUI可以檢視安裝了哪些外掛。

Vue.use 和 Vue.install使用的地方彙總到plugins目錄下

本專案是一個多頁面應用,總共有3個Vue例項,所以希望能夠按需注入想要的第三方依賴如axios,qrcode,lazyload等一些工具庫或第三方元件。於是都將其彙總到plugins目錄下了,如下圖:

-w848

-w842

這樣封裝的好處是,我可以對每個Vue例項按需匯入第三方依賴,一行import就可以,如下圖:

-w448

開始考慮優化了

一個大型的web app 打包壓縮後都不超過500kb,所以就以此作為Benchmark來優化該專案打包後的大小,按需引入第三方依賴(特別是Elementlodash)減少了不少專案體積。不過最主要的還是得學會用webpack analyse tool來分析。

結論

總之,本次專案主要是重新整理了對Vuex的認知,其能夠讓我們編寫的vue元件可讀性和可維護性更高。同時,也能夠讓資料更新這塊的程式碼複用性更高。通過Vuex來構建一個中心化的資料集散中心還能讓開發的思路更順暢,值得學習。

參考:

Vuex 是什麼?

相關文章