項目總結

業務擴展,IOS和安卓都有成型的版本,所以要做一個對應的移動端H5版的機票訂,買票應用,入口是微信公眾號,當然少不了jssdk的使用,以及balabala的授權處理等。最初是考慮用React+Redux+Webpack,前后端完全分離,但考慮到人手不足,前后端暫時做不了完全分離,然后還有對React也不熟悉,項目時間等問題,然后就被Boss否了。
最終用了更熟悉的Vue+Vuex+Webpack。主要還是因為更輕,API更加友好,上手速度更快,加上團隊里有人熟悉,可以馬上開工。比較遺憾的是因為各種原因前后端分離還不是很徹底,前端用的是jsp模板加js渲染頁面。好處是首屏數據可以放到script標簽里面直出,在進度條讀完的時候頁面就能夠渲染出來了,提高首屏渲染時間。但是調試的時候十分麻煩,因為沒有Node做中間層,每次都要在本地完整地跑個服務器,不然拿不到數據。
Vue,Vuex,Vue-router,Webpack這些不了解的同學就去看看文檔。MV*框架用好了真的是極大地解放生產力,特別是頁面的交互十分復雜的時候。

項目過程中遇到的坑

1. 遇到的第一個的坑就是transition。首頁有一個滑動的banner,我是直接用css3的transition配合js定時改變transform實現的。
滑動在chrome中模擬沒問題,ios中沒問題,但是安卓中就沒有滑動,百思不得其解。起初還以為是兼容性問題,搞了好久才發現需要在css中先增加一個transform: translateX(0)
,像下面一樣,不然之后再通過js更改transform是沒法在安卓中觸發transition的。
123456

.slide-wp{ transform: translateX(0); -webkit-transform: translateX(0); transition: transform 1.5s ease; -webkit-transition: transform 1.5s ease;}

大家知道,transition的作用是令CSS的屬性值在一定的時間區間內平滑地過渡。
所以個人猜測,在安卓中,當沒有初始值時,translateX
的改動沒有被平滑地過渡,就是說transition并不知道translateX
是從什么地方開始過渡的,所以也就沒有平滑之說,也就沒有動畫了。

2. 第二個就是ES6。既然用了Webpack,當然就要配合Bebel用上ES6啦。寫的時候還是很爽的。let
,const
,模塊,箭頭函數,字符串模版,對象屬性簡寫,解構等等…但帥不過3秒,在chrome上模擬地跑一點問題都沒有,一到移動端就直接白屏,頁面都沒有渲染出來。
排查了好久,才發現是某些擴展運算符...
,某些解構和for...of...
循環的問題。因為這些ES6的特性(其實不指這些)在Bebel中轉換是要用到[Symbol.iterator]接口的。如下面這樣。轉碼前:
12

const [a, b, c, d, e] = 'hello';console.log(a, b, c, d, e);//'h','e','l','l','o'

轉碼后:
123456789101112131415

'use strict';var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arrSymbol.iterator, _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i'return'; } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();var _hello = 'hello';var _hello2 = _slicedToArray(_hello, 5);var a = _hello2[0];var b = _hello2[1];var c = _hello2[2];var d = _hello2[3];var e = _hello2[4];console.log(a, b, c, d, e);//'h','e','l','l','o'

第一行先聲明的_slicedToArray函數用到了[Symbol.iterator]接口,然而瀏覽器對這個接口的支持還很有限,特別是移動端,只有Firefox Mobile36版本以上才支持,其它清一色掛掉。
如下圖所示:

所以說ES6雖好,但真要用到實際項目中的話,還不能太激進,有些特性經過Bebel轉碼后性能上可能還會有所下降,所以還是應該合理地使用ES6。如果是自己折騰倒無所謂,Symbol,Class,Generator,Promise這些就隨便炫技吧。

3. 第三個坑就是Vue使用的問題。如其說是坑,還是不如說是我自身還不夠熟悉Vue。先看一下官方說明:
受 ES5 的限制,Vue.js 不能檢測到對象屬性的添加或刪除。因為 Vue.js 在初始化實例時將屬性轉為 getter/setter,所以屬性必須在 data 對象上才能讓 Vue.js 轉換它,才能讓它是響應的。

當時需要在props傳來的某些對象數據中增加一個是否可視屬性,用來控制一個與其關聯的彈出框。增加后點擊視圖上一點反應都沒有,但是用console.log
打印出來發現值的確的有變化的。
也就是說,數據的變化不能觸發視圖更新。原因就是如上面所說,因為這個屬性是我后來添加的,不能被Vuejs檢測到。這時候需要使用$set(key, value)這個API。
話說里面的語法需要注意下,第一個參數key
是一個字符串,是一個keypath
,如果假如你的數據是這樣:
123456789101112

data(){ visitors : [{ "id": 1, ... }, { "id": 2, ... }, { "id": 3, ... }],}

你需要在某次操作后為visitiors
里面的每個對象增加一個show
屬性,則需要這樣寫:
12345

let str;for (let i = 0 , len = this.visitors.length ; i < len; i++) { str = "visitors[" + i + "].show"; this.$set(str,true);}

之前真的被這東西搞了很久,明明數據變化了,視圖卻不更新。個人感覺新手剛使用Vue時很難發現這個問題。也怪自己對Vue,對ES5的getter/setter
的理解還不夠吧。

4. 第四個是IOS上的滾動問題。在某些瀏覽器下,例如微信內嵌瀏覽器,手指在屏幕上滑動時,頁面會進入momentum scrolling(彈性滾動)。這時候會停止所有的事件響應DOM操作引起的頁面渲染,onscroll事件不會觸發,CSS3動畫,gif這些也不會動,一直到滑動停止。
因為需要onscroll事件來執行懶加載等操作,但是在IOS中是要等到滑動停止后才能執行的,用戶體驗不好。當時google了很久,最終得出的結論是,并沒有什么很好的解決方案。所以暫時只能在IOS上首次加載更多資源了。
貼一個在segmentfault上的答案吧,很好地總結了這個問題。(戳這里

5. 第五個還是IOS上CSS3動畫的問題,今天才遇到的。在對img或者設置了background-image的DOM元素設置CSS動畫時,動畫在剛進入頁面的時候有可能不被觸發,需要滑動一下屏幕動畫才動,安卓下則沒有問題。
剛開始還以為是沒有設置初始值的問題,但感覺不應該會是這樣的。后來在stackoverflow上找到了解決辦法(戳這里)。給動畫加個0.1s秒的延時
12

animation: slide 1.5s 0.1s linear infinite;webkit-animation: slide 1.5s 0.1s linear infinite;

原因大概是如果Safari和IOS的微信內置瀏覽器在加載資源,或者進行什么內部渲染操作時出現了短暫的停頓(英文是hiccups),則不會觸發動畫,需要滑動一下頁面來重新觸發。所以給動畫加個0.1s延時確保資源加載完成后,問題得以解決。

關于Vue的組件化
先上個@xufei大大的博客,里面有多關于組件化的文章,都是滿滿的干貨。
其實組件化是一個很龐大的話題,我這等小白的認識還十分顯淺,不過既然在項目中用到了組件化,也就談談自己的看法吧。
Vue的組件化需要配合Webpack+vue-loader 或者 Browserify + vueify 來構建。一個.vue文件一個組件,上手了寫起來是十分快捷的,不過對于新手可能就要花點時間去熟悉工具了。
之前在看了@xufei的博客加上自己的工程實踐后,表示十分贊同他的說法:
很多人會把復用作為組件化的第一需求,但實際上,在UI層,復用的價值遠遠比不上分治。

特別是對于.vue這種形式的組件化來說,想做到復用往往需要做到內部實現高度抽象,并對外暴露很多接口,而復用的地方也并不是很多。很多時候,花那么多時間去實現一個組件復用,還不如新建一個組件,復制部分代碼,重新進行內部實現來得更快。
要知道一個.vue文件里面除了<template>
、<style>
,還有<script>
。前兩者用于實現組件的樣式呈現,對于很多地方來說,可能只是有著些許差別,但<script>
里面的東西則是代表著組件的內部邏輯實現,這些邏輯往往有著很大的不同。

圖2
圖2

如上面的圖1,圖2。從樣式上看,不同的地方僅僅是圖2的每個常用乘機人多了一個復選框勾選,似乎可以通過某個標記來約定是否出現勾選框來達成組件復用。
但實際上,因為這兩個組件所身處的業務頁面的不同而存在著較大的內部邏輯實現差異。
像圖1,是在我的板塊里面的。里面僅僅是一個乘客展示和乘客信息編輯的作用,相對較為獨立。而圖2則是在訂單確認頁面里面的,除了乘客展示和乘客信息編輯外,還有選擇乘客的作用。點了保存后會與訂單狀態產生交互,而且訂單狀態的改變還會反過來影響著這些乘客信息的狀態,遠比圖1里面的復雜。
如果強行抽象中公共部分,對外暴露各種API來令該組件可復用,除了實現成本和維護成本高外,其復用得到的價值也不高。還不如寫多一個組件,復制其樣式部分,重新實現內部邏輯來得實在。而且將兩個組件放在不同的板塊內,相互獨立,也方便管理和維護。
那怎樣的組件才適合復用?我個人認為,只有很少內部邏輯的展示型組件才適合復用。像導航欄,彈出層,加載動畫這些。而其它的一些組件往往只在兩三頁面存在復用價值,是否抽象分離出來,就要看個人取舍了。

關于Vuex
Vuex 之于 vue,就相當于 Redux 之于 React。它是一套數據管理架構實現,用于解決在大型前端應用時數據流動,數據管理等問題。
因為組件一旦多起來,不同組件之間的通信和數據流動會變得十分繁瑣及難以追蹤,特別是在子組件向同級子組件通信時,你可能需要先$dispatch到父組件,再$broadcast給子組件,整個事件流十分繁雜,也很難調試。
Vuex就是用來解決這些問題的。更具體的說明可以看文檔,我就不過多敘述了。我就說一下我對Vuex的一些理解。
Vuex里面的數據流是單向的,就像官方說的那樣:
用戶在組件中的輸入操作觸發 action 調用;
Actions 通過分發 mutations 來修改 store 實例的狀態;
Store 實例的狀態變化反過來又通過 getters 被組件獲知。


而且為了保證數據是單向流動,并且是可監控和可預測的,除了在mutation handlers 外,其它地方不允許直接修改 store 里面的 state。
個人認為store就是一個類數據庫的東西,處于整個應用的最頂端,每個組件之間共享數據,并通過actions來對數據進行操作。在這樣的理解下,我更傾向于把mutations類比為查詢語句,即只在mutations里面進行增刪查改,篩選,排序等一些簡單的邏輯操作,也符合是同步函數的約束。就像這樣
12345678910111213141516171819202122232425

const mutations = { //設置常用乘機人列表 SET_PSGLIST(state, psgList){ state.psgList = psgList; }, //增加在訂單中的乘客 ADD_ORDERPSG(state, orderPsg){ for (let i = 0,len = state.orderPsgList.length; i < len; i++) { if (state.orderPsgList[i].id == orderPsg.id) { state.orderPsgList[i] = orderPsg; return; } } state.orderPsgList.push(orderPsg); }, //刪除在訂單中的乘客 REMOVE_ORDERPSG(state, orderPsg){ for (let i = 0,len = state.orderPsgList.length; i < len; i++) { if (state.orderPsgList[i].id == orderPsg.id) { state.orderPsgList.splice(i,1) return; } } }}

更復雜的邏輯則寫進actions中。例如我要在action中先異步獲取常用乘機人數據,并初始化:
12345678910111213141516171819202122232425262728293031

//actionexport const iniPsgList = ({ dispatch }, uid) =>{ let data = { uid: uid, } $.ajax({ url: "../passenger/list", data: data, success(data){ let jsonData = JSON.parse(data); let psgs = jsonData.data.passengers; dispatch('SET_PSGLIST', psgs); }, error(){ console.log('獲取常用乘機人列表信息錯誤'); } }) }//組件中調用import { iniPsgList } from './actions'const vm = new Vue({ created(){ this.iniPsgList(uid); }, vuex: { getters: { ... }, actions: iniPsgList, }})

或者,為了令actions實現得更為通用,你完全可以給每個mutation對應寫一個action,每個action就只是分發該mutation,不做其它額外的事情。然后再在組件中引入這些actions。這樣其實就相當于在組件中觸發mutations,從而減少action這個流程。
123456789

function makeAction(type , data){ return ({ dispath }, data) => { dispatch( type , data) }}export const setPsgList = makeAction('SET_PSGLIST', psgList)export const addOrderPsg = makeAction('ADD_ORDERPSG', orderPsg)export const removeOrderPsg = makeAction('REMOVE_ORDERPSG', orderPsg)

這時候初始化常用乘機人列表,則是這樣寫。
1234567891011121314151617181920212223242526

//組件中調用import { setPsgList } from './actions'const vm = new Vue({ created(){ let data = { uid: uid, } $.ajax({ url: "../passenger/list", data: data, success: (data) = > { let jsonData = JSON.parse(data); let psgs = jsonData.data.passengers; this.setPsgList(psgs); }, error(){ console.log('獲取常用乘機人列表信息錯誤'); } }) }, vuex: { getters: { ... }, actions: setPsgList, }})

兩者的區別就僅是把異步等一些更復雜的邏輯放在action中還是放在組件內部邏輯中。前者的action更有針對性,更具有唯一性;后者的action僅僅就是一個觸發mutation的作用,而組件則與更多的邏輯耦合。
誰優誰劣很難說清,和個人喜好、業務邏輯等有較大關系。我在項目中使用的是后一種用法,因為我個人更喜歡在組件實現更多的內部邏輯,方便以后針對改組件的調試和維護,免得還要在action中尋找一次。
莫名其妙地扯了這么多,其實都是一些總結與歸納。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容