使用vue一年多了,做了一個javaee的項目(全棧,前端使用的mvvm框架vue),三個移動端項目,其中兩個釘釘子應用(釘釘的坑很多,心累),一個微信的(ing)。自己也慢慢摸索出一些項目中的最佳實踐,整理了一下,做個記錄一起交流。如果你在閱讀過程中,覺得我某些地方做的不對或者有更好的方法時,歡迎交流~
項目結構
清晰的目錄結構不僅可以展現一個團隊的水平,而且別人維護(接鍋)的時候,也能更好的理解你的項目。這個每個團隊都有自己的標准或者風格,沒有固定的格式。我一般是這么安排的(vue-cli項目),下面是src目錄:
—— src
|—— assets // 項目資源目錄
|—— styles // 樣式文件
|—— reset.scss // reset css,會在 /src/main.js 中被導入
|—— variables.scss // 項目中的變量,混合(mixin)等公有樣式變量
|—— ...
|—— images // 圖片
|—— fonts // 字體
|—— ...
|—— components // 組件目錄
|—— layout // 布局相關組件
|—— Header.vue // 頭部
|—— BottomMenu.vue // 底部菜單
|—— ...
|—— common // 公有組件
|—— base // 基礎組件
|—— ...
|—— pages // 頁面目錄
|—— user // 用戶相關頁面
|—— Login.vue // 登錄頁面
|—— Register.vue // 注冊頁面
|—— Info.vue // 詳情頁面
|—— order // 訂單相關頁面
|—— List.vue // 訂單列表
|—— Detail.vue //訂單詳情
|—— ...
|—— Home.vue // 主頁
|—— ...
|—— router // 路由
|—— modules // 存放各個模塊的路由
|—— user.js // 用戶模塊
|—— order.js // 訂單模塊
|—— index.js // 路由主js,整合各個模塊,並且還會定義一些全局鈎子等其他
|—— store // 全局狀態管理目錄
|—— mutation-types.js // mutation types
|—— index.js // 主js,整合各個模塊的
|—— actions.js // actions
|—— modules // 各個模塊的states
|—— user.js
|—— order.js
|—— common // 全局工具方法
|—— data-format.js // 數據轉換
|—— http.js // 網絡請求
|—— ...
|—— App.vue
|—— main.js
|—— init-plugins.js // 依賴的第三方的初始化,會在main.js中引入
以上只是我個人的習慣,不過這個結構要根據具體的項目情況調整。不必為了模塊化而模塊化。如果你的項目業務邏輯不復雜,整個項目也就十幾個頁面,可以適當的刪減部分模塊。
組件
你的項目可能需要一些這樣的組件,來提升用戶體驗。
no-data-found
這個存在的場景是,比如你加載數據列表或者篩選列表查詢等數據操作,如果請求成功但是沒有數據,這時候有必要提供一個no-data-found的組件。
error
當你的應用出錯的時候,比如網絡超時加載不了數據,也可以給一個組件做提示
- …
方法抽取
在開始我們項目之前,我們應該知道,有哪些方法是必要的、共有的,類似於我們的工具方法一樣。這里我羅列一些供參考:
網絡請求
- 項目可能用到幾種請求?(POST,GET,PUT,DELETE…)
對於不同的請求,我們的請求頭也需要改變,是抽取成四個方法,還是在一個方法上變異?
哪種更適合現在的業務場景? - 請求出錯如何處理(組件內捕獲?全部捕獲?其他?…)
為什么提出這個問題呢?因為這是我前不久我的leader給我提的優化方案。之前的項目都沒在意過這個,后來是把axios包了一層,使用了類似jQuery的請求方式,實現了組件內捕獲業務場景錯誤(校驗失敗之類的),全部捕獲請求錯誤(404,500,400等)。
數據轉換
- 日期格式轉換
日期與字符串轉換
關於日期都所有操作,建議采用
momentJs。功能強大而且兼容性好,我深深的記得之前一個釘釘項目中日期操作不兼容iphone手機的時候,那個bug讓我找了半天才找到根源。
官網傳送門:http://momentjs.cn/數字與字符串轉換
交互方法
你的應用應該包含一套用於提示用戶的組件:
Toast
用於提示用戶一些信息
Confirm
用戶提示用戶是否確認接下來的操作。對於一些重要的操作,比如提交表單,刪除信息等操作,務必使用。
Loading
當用戶在上傳或者下載圖片等其他資源的時候,用於提示進度信息。當然這個請求方法要能夠獲得到相應的進度信息才行,偽造須謹慎(手動斜眼笑)。
Spinner
加載數據時的提示,俗稱“菊花圖“
mint-ui:http://mint-ui.github.io/docs/#/
vux:https://doc.vux.li/zh-CN/
這里要說的是,你可以對這些方法進行二次封裝,以便更方便的使用。包裝后,可能就可以像下面這樣使用了:
Dialog.toast('cool')
Dialog.confirm('Are you sure?',()=>{Dialog.toast('confirm')}) Dialog.spinner('loading...')
如何管理css
該分離還是該合並
我們知道vue的SFC(單文件組件,即以.vue結尾的文件),提供了三個基礎的頂級標簽:<template>, <style>,<script>。我個人比較喜歡把樣式也寫進SFC中,因為這樣針對性很強,調試某個頁面樣式的時候,只要打開一個文件就行了。(這么說是因為我一開始是隔離所有的css文件到assets目錄下,這樣雖然代碼看起來漂亮了,你只要在style標簽內import樣式就可以了,但是調試的時候非常不方便。)
可以使用全局變量嗎
當然,前提是在你使用css預處理器的情況下:
為了系統的UI一致性(consistency),我們的設計稿肯定是一套統一的主題的。所以我們不同頁面的邊框粗細顏色,按鈕背景色,字體大小顏色,一級邊框二級邊框…,一級標題二級標題…等等眾多的樣式需要保持一致。如果在每個頁面都定義一次,這可行,但是工作量和做法都是不值得提倡的。
有一些loader就是負責處理這類情況的,以sass為例,你可以使用 sass-resources-loader 來完成全局變量的共享。使用方法也很簡單:
- 安裝
npm install sass-resources-loader --save-dev
- 使用
如果你使用的vue-cli版本是3之前的話,修改項目中/build/utils.js中的scss成如下:
scss: generateLoaders('sass').concat({
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, '../src/assets/styles/variables.scss')
}
})
接下來就可以在任意的組件中使用這個varibles.scss里面的樣式了。
reset.css
有的ui框架會修改默認樣式,而且不同瀏覽器的樣式標准也有一些不同,所以resetcss很有必要。國內諸如淘寶,百度等都有自己的resetcss,你可以參考這篇博客獲得幫助,也可以根據這個自定義自己項目的resetcss。
https://meyerweb.com/eric/tools/css/reset/
<style scoped> with <style>
也許你遇到過這種情況,需要修改某一個ui框架的默認樣式,但是在scoped的style塊中修改不成功。不得不將style塊的scoped屬性拿掉,或者將這個要改變的樣式放進全局樣式里再在main.js中導入,或者直接將樣式定義在App.vue里面。這些都是可行的,但是要注意命名空間要是正確的,不然是找不到樣式的。
這里提供另一種方法。其實在vue的SFC中,是允許多個style塊的。你可以像下面這樣使用:
<style scoped lang="scss"> .order-detail { // put your local style here } </style>
<style> .mint-button{ // put your global style here if you want to change the lib's style } </style>
善用vue-router的meta
可能很多人用vue-router的時候,只是用來做路由跳轉,配置路由的時候,添加一個path和component就完事了。如果這樣能滿足你的所有需求,那也ok。但是有一些業務場景,使用meta屬性可以非常巧妙的解決問題。
這里分享一下我使用過的例子:
title
想要動態的顯示應用/系統的標題,你會怎么做呢?
你可能會在每一個頁面的鈎子中給document.title賦值,但是有更簡單的方法,比如我定義一個下面的router:
{ name:'user-detail', path:'/user-detail', component: UserDetail, meta: { title: '用戶信息' }
},
接着在App.vue里面,添加下面的代碼:
watch:{
'$route':function(to, from){
document.title = to.meta.title
}
}
這樣每次定義router的時候,只要填寫了meta屬性,系統的title就會跟着改變了。
另外,在App.vue里面watch的$route,還有很多用處,比如處理前進后退的動畫等。
keepAlive
我們知道vue提供了一個<keep-alive> 組件,用戶緩存組件以獲得更快的交互。簡單介紹一下,當設置某一頁面為keep-alive的時候,它的mounted只會在第一次加載的時候執行一次,后面在打開該頁面的時候都不會執行。但是activated和deactivated兩個鈎子可以捕獲頁面進入和退出的動作。
根據不同的業務邏輯,有些頁面需要緩存有些頁面不需要緩存,所以我們也可以定義在router中:
{ name:'user-detail', path:'/user-detail', component: UserDetail, meta: { title: '用戶信息', keepAlive: true }
},
然后修改主路由入口的代碼(App.vue)
<template v-if="$route.meta.keepAlive">
<keep-alive>
<router-view/>
</keep-alive>
</template>
<template v-else>
<router-view/>
</template>
這樣你就可以根據自己的需要,決定哪些頁面需要緩存了。
關於<keep-alive> 組件的更多用法,參考:https://cn.vuejs.org/v2/api/#keep-alive
scrollToTop
我們知道,vue-router提供了頁面的滾動行為。vue-router的滾動行為
我們也可以在路由中配置該屬性,來細粒度的控制我們頁面的滾動行為。
scrollBehavior方法,摘自官網例子 。我給注釋翻譯了一下。
https://github.com/vuejs/vue-router/blob/next/examples/scroll-behavior/app.js
// - 只在支持html5 history 模式的瀏覽器中有效
// - 默認無滾動行為
// - 返回false的話,會阻止滾動行為
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
// savedPosition 只在popstate的導航中有效
// popstate:https://developer.mozilla.org/zh-CN/docs/Web/Events/popstate
return savedPosition
} else {
const position = {}
// 通過返回選擇器來滾動到錨點位置
if (to.hash) {
position.selector = to.hash
}
// 檢查路由元數據(meta)配置中是否是返回頂部的需求
if (to.matched.some(m => m.meta.scrollToTop)) {
// 設置滾動位置的x和y坐標:頂部
position.x = 0
position.y = 0
}
// 如果返回的位置是假值(falsy)或者是空對象,將會返回當前滾動的位置
return position
}
}
OK,今天就說這么多,還有一些,比如異常處理,vuex使用場景,一致性(consistency)等,下次有機會在寫一篇,今天頭有點痛,得睡覺了。
有一些實踐我已經用到這個項目https://github.com/JerryYuanJ/a-vue-app-template中了,但是由於這個項目寫的時間比較早,而且主要是為了熟悉vue和其他一些練習為主,那時候還不懂事,所以很多地方還是得改的,如果你需要拿項目來練手上面的一些實踐的話,這會是個不錯的選擇。你可以pull requeset,我會merge的~歡迎star
