目錄結構
總覽
-
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?
元件的設計在業務層非常的重要,在下一篇我會介紹一下我總結出的一些實踐