總結下Vue開發的心得(上)
(一直想寫,今天終於有機會了……)
17年大概做了4個 Vue 專案,2個後臺管理系統,一個純移動端,一個內嵌在原生 App 的專案。中途還參與了一個 React 專案,自己也用 React 寫過兩個 demo(能更好的發現二者的異同點)。從而決定要寫這篇文章,有寫的不對的地方,求大佬親噴。(預設大家都掌握了Vue, Vuex, Vue-router 及其相關周邊庫)
一、從拿到原型圖到UI圖的一些準備工作
專案是要內嵌於原生 App 中,所以分析需求的時候,先得確定哪些東西是要交給原生來負責。(PS:專案是一款知識付費的型別的 Hyprid App)
首先 H5 和原生 WebView 通訊的原理,是在window下掛載一個物件,這裡直接貼程式碼吧
const
postMessage
=
(
method
,
message
)
=>
{
if
(
window
。
AndroidWebView
)
{
// Android
if
(
message
)
{
window
。
AndroidWebView
[
method
](
message
)
}
else
{
window
。
AndroidWebView
[
method
]()
}
}
else
{
// iOS
window
。
webkit
。
messageHandlers
。
a
。
postMessage
({
body
:
{
method
,
message
}
})
}
}
接著將這個方法掛載到 Vue 下,就可以全域性使用啦(PS:記住 message 裡面的型別一定要是String)
Vue
。
prototype
。
$postMessage
=
postMessage
// 使用的時候很簡單
const
methodName
=
xxx
const
toAppJson
=
{}
this
。
$postMessage
(
methodName
,
JSON
。
stringify
(
toAppJson
))
比如調起原生的支付、分享,跳轉原生頁面,還有一些特殊的功能,也需要兩邊相互配合才可以實現,比如自動播放,自動聚焦的同時喚起鍵盤。不僅 web 端可以去調原生的方法,反過來也是可以的。
在支付成功,取消,失敗後,或者分享後,來呼叫web端的方法
window
。
payCallback
=
(
type
)
=>
{
switch
(
type
)
{
//。。。跳轉路由
}
}
window
。
onShare
=
(
userId
,
type
)
=>
{
// 給分享成功的使用者加積分
}
還有最重要的一個功能,大多數 App,都有一個註冊登陸的功能,但是內嵌的我們只需要一個‘假授權’登陸,即原生端將使用者資訊,存在 cookie,localStorage 裡,取出來以後做加密後,請求登陸驗證即可。這裡不建議把使用者資訊拼接在URL後面,因為在安卓手機下,使用者斷網訪問 Webview 會暴露整個 URL 資訊出來。而且兩者有個細微的區別,就是在取引數的時候,前者,必須等待整個 Webview 載入完成之後,才可以取到,所以我需要加一個延遲。
開發內嵌在 web,可能會遇到意想不到的 bug,所以你需要你能在手機上檢視的控制檯。(公司大佬推薦給我的,超級好用)
// 載入控制檯
export
const
loadScript
=
(
url
,
callback
)
=>
{
const
script
=
document
。
createElement
(
‘script’
)
script
。
onload
=
()
=>
callback
()
script
。
src
=
url
document
。
body
。
appendChild
(
script
)
}
loadScript
(
‘https://res。wx。qq。com/mmbizwap/zh_CN/htmledition/js/vconsole/3。0。0/vconsole。min。js’
,
()
=>
{
// eslint-disable-next-line
new
VConsole
()
})
複製以上程式碼,在 main。js 引入,在手機上執行,即可出現如下按鈕。
點開它,你就可以看的這個畫面
紅色框框標註,有什麼用,我先賣個關子,後面詳細說明。。。
二、技術棧,外掛庫,CSS方案選擇
知識付費主要的功能是音訊播放,資料蒐集,統計,展示。付費使用者和非付費使用者所展示的介面,播放器的幾種狀態,使用者的留言,評論,點贊等等。。。
首先說一下UI庫的選擇,移動端截止目前可供選擇的還是蠻多的:
‘YDUI‘(一隻基於Vue2。x的移動端&微信UI。 -YDUI Touch)
最新版本,還處於公測階段。我也是最近一段時間才聽說這個款,並沒有深入使用過,只是簡單看過文件,有做個專案的小夥伴可在評論區說一下,另外按需載入配置起來略複雜。
‘有贊UI‘(Vant - 有贊移動端 Vue 元件庫)
第一次瞭解這個庫,是在掘金上(如果沒記錯的話),有PC版(基於React)、移動端以及小程式版。最近有兩個小專案,正在使用它開發。週四提了個issue,作者也是積極修復,立馬就迭代了一個版本,這波必須打call。
採用目前主流的
babel-plugin-import
外掛實現按需載入,不需要額外引入css,樣式很好看。元件定位也很明確,就是幫助你迅速搭建一個商城,強烈推薦。
‘mint-ui‘(mint-ui documentation)
餓了麼團隊出品,按需載入使用得是
babel-plugin-component
,在前端發展如此迅速的今天,可以說是比較‘古老’的元件庫,樣式也不夠炫。據知情人士不可靠訊息稱,餓了麼團隊今年會重新開發一套移動端的元件。
‘妹紙UI‘(Muse-UI)
摸著良心說,這絕對是我目前使用過Vue UI庫裡,顏值最高的,沒有之一。整體設計偏像國外的風格。而且感覺它不只針對移動使用,還能用於PC端。文件也寫的非常漂亮。非要說一個缺點嘛,就是按需載入配置起來很麻煩。我折騰了一天,並沒有成功,移動端流量第一,沒解決這個問題,所以我只好放棄,都怪我太菜,不會配置webpack。
‘WeUI‘(VUX - 移動端Vue元件庫)
所以,最後我選擇了Vux作為這個款web App唯一指定合作伙伴,你要問我它好在哪裡,我可能說不出來,但是,真的是一款為開發移動端專案的精心準備的禮物。
細緻入微的文件
移除移動端點選延遲、新增 viewport meta、微信jssdk、非同步載入元件、webvView常見問題、加密工具、防抖和節流、1-px、日期格式化等等這些。對於第一次開發移動端的新手來說,是非常全面的
有很多成功的例子,有興趣的同學可以去官網看看
超級豐富的元件庫,我沒有具體統計過。但是如上列舉的5個常用庫,它的元件最多。
(PS:使用 Cell 元件裡左側 icon,在低版本的安卓手機會有 bug,具體要看那款手機用的Chrome核心是多少,反正版本 30 以下會出現bug,即子元素沒辦法撐開父元素)
第一張是高版本瀏覽器核心開啟的效果,後面兩張就是 Chrome30- 的效果了。這裡說一下我是如何定位這個問題,因為這個 bug 只出現一臺安卓系統4。4的手機,起初我以為是它的問題,但是呢有的又不會。這個時候,我上面提到那個真機除錯控制檯就展示效果了,我發現出現這個佈局錯亂的原因是由於瀏覽器核心造成的。但是隻定位了一個大概,那麼具體原因是啥,我們還得進一步分析。是 position 定位?flex 不支援?vw 單位不支援?都不是!
為了解決這個問題,終於讓我找的了谷歌瀏覽 29,和 30 的版本的下載包。當時還不支援切換手機模式,終於讓我發現原因,是沒給父元素設高寬。可能是我 CSS 比較菜,或者是vux作者也沒有想到這個點。
這次解決問題的經歷還是挺有趣的,下次有機會面試別人,可以給他兩部安卓手機和一部ios手機,讓他調這個bug。看看他會如何分析,排查這個bug。
最後專案開發時間為一個半月,然後上了40天班。對於敏捷開發來說,在選取庫的時候,儘量選擇團隊成員都瞭解,或者自己特別熟悉的。避免遇到突發情況,從而耽誤開發進度。圖表工具自然選擇業界良心的 echarts(ECharts),資料通訊 axios (axios/axios),狀態管理(Vuex 是什麼? · Vuex)後面我會詳細說一下使用心得。
最後是 CSS 方案,一直以來都是用的 less,要為什麼,我覺得它簡單實用易上手。整個專案佈局,都是以 flex 為主,以後有機會會嘗試grid。還有一個重要的點,是如何做自適應佈局,大家可能都習慣用 rem。js,或者是淘寶那套高畫質方案。但是結合具體專案,我們的產品面向的是安卓4。4+,iOS8。0+,考慮到是一款知識付費,以及開發成本,低於這個版本的使用者,在體驗上的不友好不在考慮範圍之內,所以我選擇了採用 vw 方案實現自適應。
設計稿是以為 iPhone6 為準的。那個 0。133vw 是在 100vw / 750 得到的。後面開發的時候,只需要引入這個 less 檔案,然後把設計稿上標註由 px 替換成 * @vw 即可。
三、專案結構
以上兩個點,我自己感覺講的亂七八糟的,後面儘量保證清晰一點。(簡單的就略過了)
src 下的目錄結
|
|——
assets
|
|——
images
/* 圖片 */
|
|——
less
/* 公共樣式 */
|
|
|——
common
。
less
|
|
|——
variable
。
less
|
|
|——
reset
。
less
|
|
|——
index
。
less
|
|
|
|——
components
/* 元件 */
|
|——
filter
/* 格式化時間,數字,字元的函式 */
|
|——
mixins
/* 不解釋 */
|
|——
router
/* 不解釋 */
|
|——
store
|
|
|——
modules
/* 按模組拆分狀態 */
|
|
|——
index
。
js
|
|——
svg
/* svg */
|
|——
views
/* 頁面路由對應的元件 */
|
|——
App
。
vue
|
|——
dev
-
config
。
js
/* 開發階段引入的配置檔案 */
|
|——
main
。
js
|
|——
util
。
js
/* 工具函式封裝 */
|
|
|——
changelog
。
md
/* 它不在src下,但是值得提一下 */
components
最新的 Vue 官網也推出了 beta 版的風格指南,這裡我以及整個開發團隊的風格可能有些不一樣。如何樣式超過 100 行,會單獨分離出來便於維護
|—— components
| |—— Recommend
| | |—— recommendItem。vue
| | |—— recommendList。vue
| |—— LeaveMessageItem
| | |—— index。vue
| | |—— index。less
| |—— WhiteSpace
| | |—— index。vue
filter
會在 main。js 中全部引入
import
moment
from
‘moment’
// 處理文字過長的問題
const
ellipsisText
=
(
val
,
num
)
=>
{
if
(
!
val
)
return
‘’
const
len
=
val
。
toString
()。
length
return
len
>
num
?
val
。
substring
(
0
,
num
)
+
‘。。。’
:
val
}
// 格式化留言的日期
const
isToday
=
(
val
)
=>
{
if
(
!
val
)
return
‘’
const
today
=
moment
()。
format
(
‘YYYY MM DD’
)
const
yesterday
=
moment
()。
subtract
(
1
,
‘d’
)。
format
(
‘YYYY MM DD’
)
const
temp
=
moment
(
val
)。
format
(
‘YYYY MM DD’
)
if
(
today
===
temp
)
{
return
‘今天 ’
+
moment
(
val
)。
format
(
‘H:mm’
)
}
else
if
(
yesterday
===
temp
)
{
return
‘昨天 ’
+
moment
(
val
)。
format
(
‘H:mm’
)
}
else
{
return
moment
(
val
)。
format
(
‘YYYY-MM-DD H:mm’
)
}
}
// 返回當前時間(格式:2018-01-09)
const
currDate
=
()
=>
moment
()。
format
(
‘YYYY-MM-DD HH:mm:ss’
)
// 。。。
export
{
isToday
,
ellipsisText
,
currDate
// 。。。
}
mixins
這裡就舉一個例子,專案中常常會有一些功能,又不便於寫成元件,因為它並非實實在在的頁面結構。我們可以抽成一個一個 mixin。比如在長列表,需要一個回到頂部的按鈕,但是在下滑過程中我們需要將它隱藏,因為此刻使用者用途並不想回到頂部。又比如播放器處於播放狀態時,使用者下滑時,應該隱藏或者切換成迷你狀態。
const
selfTouch
=
{
data
()
{
return
{
startx
:
‘’
,
starty
:
‘’
,
touchStatus
:
‘未滑動’
,
visible
:
false
,
timer
:
null
}
},
watch
:
{
touchStatus
(
val
)
{}
},
methods
:
{
// 獲取角度
getAngle
(
angx
,
angy
)
{},
// 根據起點終點返回方向 0-未滑動 1-向上 2-向下 3-向左 4-向右
getDirection
(
startx
,
starty
,
endx
,
endy
)
{},
// 滑動開始
selfTouchstart
()
{},
// 滑動結束
selfTouchend
()
{},
// 回到頂部
toUp
()
{}
}
}
export
{
selfTouch
}
views
這裡先講它,是因為 router 和 store 檔案下的書寫和命名都是基於它而來的
|—— views
| |—— module1
| | |—— course-list
| | |—— course-detail
| | |—— course-leave-message
| | |—— course-leave-message-detail
| | |—— index。vue
| |—— module2
| | |—— play-list
| | |—— task-list
| | |—— index。vue
| |—— module3
| | |—— coupon
| | |—— history-record
| | |—— common-problem
| | |—— index。vue
資料夾命名都是小寫,多單詞采用‘-’連結,為了跟路由的path寫法一致,每個檔案對應的就是一個路由,只允許出現以 index。vue 命名的元件,less 規則和 component 是一致。
router
import
Vue
from
‘vue’
import
Router
from
‘vue-router’
// 懶載入
const
_import
=
file
=>
()
=>
import
(
‘@/views/’
+
file
+
‘index。vue’
)
const
Module1
=
_import
(
‘module1’
)
const
CourseList
=
_import
(
‘module1/course-list’
)
const
CourseDetail
=
_import
(
‘module1/course-detail’
)
const
CourseLeaveMessage
=
_import
(
‘module1/course-leave-message’
)
// 。。。
const
Module2
=
_import
(
‘module2’
)
// 。。。
const
Module3
=
_import
(
‘module3’
)
// 。。。
Vue
。
use
(
Router
)
/**
* path - 路由linkUrl
* name - 路由名字
* component - 對應的元件名
* meta - 用來判斷是否快取整個路由元件
* params - 路由路徑的動態引數,儘量保證欄位和後端定義的一致
*/
export
default
new
Router
({
mode
:
‘history’
,
routes
:
[
{
path
:
‘/’
,
redirect
:
‘/module1’
,
name
:
‘模組1’
,
component
:
Module1
},
{
path
:
‘/module1/course-list’
,
name
:
‘課程列表’
,
component
:
CourseList
,
meta
:
{
keepAlive
:
true
}
},
{
path
:
‘/module1/course-leave-message/:courseId’
,
name
:
‘課程留言’
,
component
:
CourseList
},
})
在 main。js 也有 router 的相關程式碼,主要是為了實現產品的要求的功能
// 修改title的值
router
。
beforeEach
((
to
,
from
,
next
)
=>
{
if
(
to
。
query
。
text
)
{
// 一些動態的標題,比如:
// ‘回覆XXX’,‘XXX人點過贊’,付費與免費跳轉的路由不同卻要保證標題一致
document
。
querySelector
(
‘title’
)。
innerText
=
to
。
query
。
text
}
else
{
document
。
querySelector
(
‘title’
)。
innerText
=
to
。
name
}
next
()
})
// 滾動到頁面頂部
router
。
afterEach
((
to
,
from
)
=>
{
if
(
to
。
name
===
‘首頁’
||
‘推薦頁’
||
‘某某模組’
)
window
。
scrollTo
(
0
,
0
)
})
未完待續。。。