Vue 工程化最佳實踐

Max發表於2018-12-14

目錄結構

總覽

  • api目錄用於存放 api請求,檔名與模型名稱基本一致,檔名使用小駝峰, 方法名稱與後端restful控制器一致.

  • enums 目錄存放 常量, 與後端的常量目錄對應

  • icons目錄用於存放圖示, element-ui提供的圖示實在是太少啦.所以我通常會使用 阿里的iconfont
  • lang目錄存放多語言
  • layouts目錄存放佈局
    • 上面展示的是一個後臺系統, empty為一個空佈局.用於登入頁面, 其他頁面則使用default佈局. 佈局不需要過多介紹,寫過laravel blade都很熟悉了.這裡的佈局需要和vue-router配合使用
  • mixins 類似php的trait, 但是它更強大, 完整貼合vue元件的生命週期
  • plugins 目錄存放外掛配置, 比如 axios,vue-lazy等 (這是從nuxt中學到的概念)

  • router目錄存放與 前端路由相關的配置,總體來說類似於laravel的api層
  • store 目錄即vuex的目錄, 類似於前端的model. 其檔案與後端model相匹配,採用小駝峰命名

  • utils 目錄存放輔助函式
  • views 為業務檢視層,相信後端同學也很熟悉.其由vue-router直接排程
  • main.js 為app的入口, 類似於後端的index.php
  • components 目錄, 存放元件.通常是一些可複用的元件會單獨存放在該目錄

總體來說, 已後端的mvc思想來看現代的前端專案是非常的自然的. 後端的model對應前端的store, 後端的router對應前端的router,後端的controller + views 對應前端的views.

基礎規範

就目前來說 vue專案很少用到 class, 因此 .js檔案通常都是一個 module, 所以檔名使用小駝峰的形式命名. 如果有類檔案,則類檔案使用大駝峰的形式命名.

.vue檔案 可以使用 中劃線和大駝峰兩種命名方式, 參考了element/iview/nuxt專案之後, 推薦統一使用中劃線命名.

所有的資料夾名稱統一使用中劃線命名

引入vue元件時檔案時需要轉換成大駝峰import 'TestTest' from '@/components/test-test'

在template 使用時依舊使用中劃線

<test-test />

其他規範如變數命名和使用規範 使用eslint的standard 即可很好的解決.

前端存在很多的事件 如change/input/upload/sumit等等,相應的處理推薦使用 handle + 事件名稱, 如handleChange

生命週期

vue-router 解析當前使用者鍵入的 url, 然後匹配合適的檢視元件載入.

著重介紹一下 我對views目錄下的檢視元件的理解,已修改地址為例

script部分既控制器部分,其請求資料, 然後注入到view 中, 就像後端的mvc一樣.只不過vue將 vc 其寫入到了一個檔案中.這樣理解對於寫過後端的同學顯得更加的自然

控制器如何獲取資料?

在過去的vue專案中,我們可能會見到這樣的寫法

// ... views/address/edit.vue
created () {
    axios.get('/addresses/1')
        .then((response) => {
             this.list = resposne.data              
        })
}

//...

這種寫法無異於後端在控制器中寫sql語句一樣,在工程化實踐中不推薦這麼做,後端通過model來獲取資料會更加的優雅自然.在vue專案中, model既vuex,因此推薦這麼做

如果你對我說的東西一臉懵逼, 那麼你可以看一下 vuex的文件. 我現在做的就是用後端熟悉的概念,來描述前端專案的最佳實踐

// ... views/address/edit.vue 控制器+檢視
computed: {
    address: () => this.$store.address.itemBy[1] // 從store模型中取出我們想要資料
}
// ...
// ... store/modules/address.js  資料來源
export default {
    state: {
        itemBy: {}
    },
    actions: {
        ...
    },
    mutations: {
        ...
    }
}
// ...
store中的資料從哪裡來?

資料當然是從後端的資料庫中獲取,我們不能讓前端直接訪問我們的資料庫,因此我們會提供api讓前端訪問.store中存在一個發起api請求的地方,既 action.

// ... store/modules/address.js  模型
export default {
    state: {
        itemBy: {}
    },
    actions: {
        async fetchItem({ commit, state }, { id }) {
             // 對axios和api進行了簡單的封裝,使api請求更加語義化
            cosnt { data } = await address.show(id)

            // action只能通過提交commit來修改state,具體原因請檢視vuex文件 (其實我也忘了為啥 (╯﹏╰))
            commit('SET_ITEM', data) 
        }
    },
    mutations: {
        SET_ITEM: (state, item) => {
            state.itemBy[item.id] = item
        }
    }
}
// ...

這樣我們的模型中就有資料啦

什麼時候去呼叫fetchItem去請求後端呢?

https://router.vuejs.org/zh/guide/advanced/data-fetching.html vue-router文件的解答

這裡不推薦在vue原始的生命週期中去呼叫初始化請求,可能會帶來 資料還沒有獲取到,template卻已經被渲染.會造成一些資料不存在的異常,推薦在vue-router的生命週期中去請求資料

// ... views/address/edit.vue
async beforeRouteEnter (to, from, next) {
    // 等待模型資料載入完畢,才繼續進行vue元件的生命週期
    await store.dispatch('fetchItem', to.params.id) 

    next()
}
created () {
    // 不推薦在這裡呼叫 fetchItem
}

//...

到這裡你可能發現,這和你平時寫的vue有些不一樣, 沒有類似 this.data = response.data 這種操作. 類似這種操作其實類似賦值操作,或者稱為副作用,其引入了時間的概念,使資料的管理變的複雜. 直觀的體現就是我們可能會有這種多餘的 if(data)判斷.

當然副作用是難以避免的,但是我們可以統一的管理他們.類似上面的程式碼就是一套我覺得還不錯的方法.從view的角度看, 資料是固有存在存在的,其不需要關心是否是否已經被載入完畢,且store中的不可被view修改,既資料只能單向流動

在store中統一管理資料的另外一個好處就是方便持久化

view層如何修改資料來源?

上面的描述實際上表達了一種 釋出與訂閱的模式, 從store到view的資料流是嚴格單向資料流動.

view層不允許直接修改store中的資料,但是view層卻可以通過傳送action來影響資料來源.

比如初始化時的dispatch的action,各種event觸發的dispatch. 當資料來源發生改變時,作為訂閱者的view層會非常自然的重新渲染.

這種設計和父子元件類似,vue中子元件不允許直接修改父元件props到子元件的資料,只能通過向父元件emit event. 在view和store之間,這種設計依然合理.

這也意味著應用中所有的資料都遵循相同的生命週期,這樣可以讓應用變得更加可預測且容易理解。

上面的圖很好的闡述了這種開發模式. 引自 https://github.com/sorrycc/blog/issues/1

view層再深入

view層的script部分,除了充當了傳統的controller,起到初始化的作用外,實際上還做了更多的事情.

先從data部分說起,在view層會有一些狀態需要記錄, 如 選單的展開或收起, 彈窗的彈出與關閉. 對於這樣的狀態的管理,一種做法就是將儲存在data部分.

也有人將所有的狀態 也放在 store中的state維護. 既state分為狀態和資料 兩種型別.

view層的script更重要的部分,是其到了一個互動反饋的作用, 既類似下面的程式碼

<template>
    <button @click="handleSubmit"/>
</template>

<script>
    export default {
        data: {},
        methods: {
            handleSubmit() {

            }
        }
    }
</script>

關於css部分,由於個人不瞭解css,也不清楚css的業界規範及在vue上的最佳實踐,因此不做過多介紹.

總結一下

在前面的介紹中, store和api 目錄是和資料掛鉤的,當資料庫定下來,這一部分也就定了下來.

views/components 和業務(ui)掛鉤,需要等設計稿確定後,這一部分才能確定下來.

PS設計稿的圖層通常就是元件的拆分規範 ?
使用vuex儲存資料的另一個好處就是可以無縫的切換到ssr框架nuxt

views和store之間是一種訂閱和釋出的模式.

兩個問題

store中的state應該如何組織? 對於api請求,我們經常會看到這樣的json資料

// post
{
    id: 1,
    title: xxxx,
    content: xxxx,
    user: {
        id: xxx,
        nickname: xxx,
        avatar: xxx,
    },
    comments: [
        {
            id: xxx,
            user_id: xxx,
            content: xxx,
            user: {
                // ...
            }
        },
        {
            //....
        }
    ]
}

上面的資料結構複雜,巢狀深入. 如果我們將他們一股腦的存在 post的state中,會造成資料過於集中,冗餘. comments無法獨立化更新等等問題. 使得前端 scheme/orm,資料組織的規範化變的迫切需要

但是vue在這方面沒有很好的規範和最佳實踐. react在這方面比較不錯的實踐 https://github.com/paularmstrong/normalizr

如何設計良好規範的compoents?

元件的設計在業務層非常的重要,在下一篇我會介紹一下我總結出的一些實踐

相關文章