總結下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 引入,在手機上執行,即可出現如下按鈕。

總結下Vue開發的心得(上)

點開它,你就可以看的這個畫面

總結下Vue開發的心得(上)

紅色框框標註,有什麼用,我先賣個關子,後面詳細說明。。。

二、技術棧,外掛庫,CSS方案選擇

知識付費主要的功能是音訊播放,資料蒐集,統計,展示。付費使用者和非付費使用者所展示的介面,播放器的幾種狀態,使用者的留言,評論,點贊等等。。。

首先說一下UI庫的選擇,移動端截止目前可供選擇的還是蠻多的:

‘YDUI‘(一隻基於Vue2。x的移動端&微信UI。 -YDUI Touch)

最新版本,還處於公測階段。我也是最近一段時間才聽說這個款,並沒有深入使用過,只是簡單看過文件,有做個專案的小夥伴可在評論區說一下,另外按需載入配置起來略複雜。

‘有贊UI‘(Vant - 有贊移動端 Vue 元件庫)

第一次瞭解這個庫,是在掘金上(如果沒記錯的話),有PC版(基於React)、移動端以及小程式版。最近有兩個小專案,正在使用它開發。週四提了個issue,作者也是積極修復,立馬就迭代了一個版本,這波必須打call。

總結下Vue開發的心得(上)

採用目前主流的

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,即子元素沒辦法撐開父元素)

總結下Vue開發的心得(上)

總結下Vue開發的心得(上)

總結下Vue開發的心得(上)

第一張是高版本瀏覽器核心開啟的效果,後面兩張就是 Chrome30- 的效果了。這裡說一下我是如何定位這個問題,因為這個 bug 只出現一臺安卓系統4。4的手機,起初我以為是它的問題,但是呢有的又不會。這個時候,我上面提到那個真機除錯控制檯就展示效果了,我發現出現這個佈局錯亂的原因是由於瀏覽器核心造成的。但是隻定位了一個大概,那麼具體原因是啥,我們還得進一步分析。是 position 定位?flex 不支援?vw 單位不支援?都不是!

為了解決這個問題,終於讓我找的了谷歌瀏覽 29,和 30 的版本的下載包。當時還不支援切換手機模式,終於讓我發現原因,是沒給父元素設高寬。可能是我 CSS 比較菜,或者是vux作者也沒有想到這個點。

總結下Vue開發的心得(上)

這次解決問題的經歷還是挺有趣的,下次有機會面試別人,可以給他兩部安卓手機和一部ios手機,讓他調這個bug。看看他會如何分析,排查這個bug。

最後專案開發時間為一個半月,然後上了40天班。對於敏捷開發來說,在選取庫的時候,儘量選擇團隊成員都瞭解,或者自己特別熟悉的。避免遇到突發情況,從而耽誤開發進度。圖表工具自然選擇業界良心的 echarts(ECharts),資料通訊 axios (axios/axios),狀態管理(Vuex 是什麼? · Vuex)後面我會詳細說一下使用心得。

最後是 CSS 方案,一直以來都是用的 less,要為什麼,我覺得它簡單實用易上手。整個專案佈局,都是以 flex 為主,以後有機會會嘗試grid。還有一個重要的點,是如何做自適應佈局,大家可能都習慣用 rem。js,或者是淘寶那套高畫質方案。但是結合具體專案,我們的產品面向的是安卓4。4+,iOS8。0+,考慮到是一款知識付費,以及開發成本,低於這個版本的使用者,在體驗上的不友好不在考慮範圍之內,所以我選擇了採用 vw 方案實現自適應。

總結下Vue開發的心得(上)

設計稿是以為 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

})

未完待續。。。