最近,Qwintry開發(fā)團(tuán)隊(duì)把很多項(xiàng)目都遷移至Vue.js,包括所有遺留的項(xiàng)目和新開始的項(xiàng)目:
遺留的Drupal系統(tǒng)(qwintry.com)新的qwintry.com分支,該分支是對舊有項(xiàng)目的徹底重寫基于Yii 2的B2B系統(tǒng)(logistics.qwintry.com)其它大大小小的內(nèi)部和外部項(xiàng)目(大部分使用PHP和Node.js作為后端)Qwintry在全球有差不多五十萬用戶,我們在美國和德國各有一個(gè)倉庫,而且我們是美國最大的貨物轉(zhuǎn)運(yùn)公司,業(yè)務(wù)主要面向東歐和中東地區(qū)。
簡單地說,我們幫助上述地區(qū)的人們購買美國網(wǎng)上商店的商品,并幫他們節(jié)省跨國運(yùn)費(fèi)。我們使用自己的IT系統(tǒng)和物流系統(tǒng)為他們提供高質(zhì)量的轉(zhuǎn)運(yùn)服務(wù),而且費(fèi)用是非常實(shí)惠的。
我們的系統(tǒng)有很多代碼,大部分是PHP和JS代碼。
我們分別使用React、Vue.js和Angular 2構(gòu)建了一個(gè)用戶計(jì)算器作為對比實(shí)驗(yàn),經(jīng)過比較之后,我們最終決定采用Vue.js。
React.js之我見
React的出現(xiàn)震驚了JS世界,幾乎成為JS開發(fā)的首選框架。
我使用React構(gòu)建了一些單頁應(yīng)用和動(dòng)態(tài)控件,我也玩過React Native(iOS)和Redux。我認(rèn)為,對于面向狀態(tài)的應(yīng)用來說,React再合適不過了,而且React為我們提供了真正的面向函數(shù)編程。React Native在很大程度上改變了原生應(yīng)用的開發(fā)。
對我來說,React的不足之處在于:
純凈、不可變性和解決問題的意識形態(tài)
不要誤解,我其實(shí)很感激React所帶給我們的純凈的函數(shù)編碼方式和簡潔的渲染手法,在實(shí)際應(yīng)用中,這些都算得上好東西。我想說的是其它方面的東西。
如果你的公司里有千人開發(fā)團(tuán)隊(duì),而剛好你決定要為PHP里的靜態(tài)類型開發(fā)自己的語法,又或者你正從Haskell轉(zhuǎn)向JS,那么一定程度的嚴(yán)格和純凈是非常有用的。不過大部分公司不會有那么大規(guī)模的開發(fā)團(tuán)隊(duì),也不會有Facebook那樣的宏大目標(biāo)。下面我會更詳細(xì)地解釋這一點(diǎn)。
JSX糟糕透了
我知道,它“不過是具有特殊語法的javascript罷了”。我們的前端設(shè)計(jì)人員為了做出漂亮的表單,把表單元素放置在div里面,他們根本不關(guān)心什么純凈或ES6。設(shè)計(jì)React組件仍然需要耗費(fèi)大量的時(shí)間,因?yàn)镴SX的可讀性太差。還有一個(gè)不好的地方,就是你無法在HTML代碼里使用普通的IF語句。React的忠實(shí)用戶會告訴你說,有了三元運(yùn)算,就不需要再使用條件判斷了。不過我向你們保證,當(dāng)你再次閱讀或編輯這些代碼時(shí),你會發(fā)現(xiàn)它們?nèi)匀皇荋TML和JS的混合體,盡管它們可以被編譯成純粹的JS。
- {items.map(item =>
- {item.name}
- )}
很多開發(fā)者認(rèn)為這種嚴(yán)格的限制可以幫助我們寫出更加模塊化的代碼,因?yàn)槲覀儽仨毎汛a塊放到工具函數(shù)里,并在render()方法里調(diào)用,就像這個(gè)人建議的那樣:
http://stackoverflow.com/a/38231866/1132016。
JSX甚至讓我們不得不把15行的HTML代碼分成3個(gè)組件,每個(gè)組件里包含5行代碼。
不要認(rèn)為這種做法會讓你成為更好的開發(fā)人員,你只是不得不這么做而已。
而事實(shí)其實(shí)是這樣的:
如果你正在開發(fā)一個(gè)相對復(fù)雜的組件,而你并不打算明天就把它發(fā)布到GitHub上,那么上述的方式只會拖你的后腿,特別是在你要解決真實(shí)的業(yè)務(wù)問題時(shí)。不過不要誤會,我并不是說拆分成小組件就一定不好。
你當(dāng)然清楚通過拆分可以提升代碼的可管理性和可重用性。但前提是,只有當(dāng)業(yè)務(wù)邏輯實(shí)體可以被放到一個(gè)單獨(dú)的組件里時(shí)才要這么做,而不是每寫一個(gè)三元操作代碼就要進(jìn)行拆分!每次在創(chuàng)建新組件時(shí)都會讓你和你的意識流付出一定的代價(jià),因?yàn)槟阈枰獜臉I(yè)務(wù)思維(在你記住當(dāng)前組件狀態(tài)時(shí),需要增加一些HTML代碼讓它運(yùn)行起來)轉(zhuǎn)換到“管理思維”——你需要為這個(gè)組件創(chuàng)建單獨(dú)的文件,考慮如何給新組件添加屬性,并把它們跟組件狀態(tài)映射起來,還要考慮如何把回調(diào)函數(shù)傳遞進(jìn)去,等等。
你被迫使用這種過度且不成熟的組件模塊化方式來編寫代碼,從而降低了編碼速度,而從中得到的模塊化可能并非你所需要的。在我看來,不成熟的模塊化跟不成熟的優(yōu)化沒有什么兩樣。
對于我和我的團(tuán)隊(duì)來說,代碼的可讀性是非常重要的,不過是否能夠從編碼中獲得樂趣也很重要。為了實(shí)現(xiàn)一個(gè)簡單的計(jì)算器控件而去創(chuàng)建六個(gè)組件,這樣的事情一點(diǎn)也不有趣。大多數(shù)情況下,這樣做也不利于維護(hù)、修改或控件檢修,因?yàn)槟阋诤芏辔募秃瘮?shù)間跳來跳去,逐個(gè)檢查每一個(gè)HTML小代碼塊。再次強(qiáng)調(diào),我并不是在建議使用單體,我只是建議在日常開發(fā)當(dāng)中使用組件來替代微組件。這是常識性問題。
在React里使用表單和Redux會讓你忙得停不下來
還記得嗎,React的設(shè)計(jì)在于它的純凈以及干凈的單向數(shù)據(jù)流。這就是為什么LinkedStateMixin不受待見,你需要為10個(gè)輸入創(chuàng)建10個(gè)函數(shù),而80%這樣的函數(shù)只包含了一行this.setState()代碼,或者一次redux調(diào)用(或許你還需要為每個(gè)輸入創(chuàng)建一個(gè)常量)。如果只要在腦子里想想就能自動(dòng)生成這些代碼的話,或許還是可以接受的,但現(xiàn)在還沒有哪個(gè)IDE可以提供這樣的功能。
為什么要敲這么多的代碼呢?因?yàn)殡p向綁定被認(rèn)為是不安全的,特別是在大型應(yīng)用里。我可以肯定地說,雙向數(shù)據(jù)流的代碼可讀性確實(shí)不是很好,而Angular 1的雙向綁定更是糟糕透頂。不過,這些還算不上大問題。
最近我用Vue.js為Drupal網(wǎng)站開發(fā)了一組快速編輯器組件:
由于一些眾所周知的原因,我不能把代碼分享出來,不過我可以說使用Vue真的很有趣,而且代碼可讀性很好。而且我可以肯定地說,在React里開發(fā)這種控件,為每個(gè)輸入創(chuàng)建一個(gè)單獨(dú)的函數(shù),我一定會感到很痛苦。
Redux看起來更像是啰嗦的代名詞。開發(fā)人員抱怨Mobx把React變成了Angular,就因?yàn)樗褂昧穗p向綁定——可以參見之前講到的第一點(diǎn)。似乎很多聰明人只是讓他們的代碼看起來更純凈,但是并沒有完成更多的事情(不過如果你沒有截止期限或許問題不大)。
過度的工具綁定
React有點(diǎn)亂糟糟的。如果離開了一大堆npm包和ES5編譯器,要做出React應(yīng)用簡直是寸步難行。基于React官方基礎(chǔ)包開發(fā)的一個(gè)簡單應(yīng)用在node_modules目錄下包含了大約75MB的JS代碼。
這不算什么大問題,它更像是JS世界的事情,不過使用React仍然只會增加我們的挫折感。
Angular 1:太多的自由有時(shí)候不是好事
相比React所強(qiáng)調(diào)的所謂JS純凈性和代碼可讀性,Angular 1算得上一款優(yōu)秀的前端框架。Angular 1可以幫助我們快速進(jìn)入開發(fā),在代碼的頭一千行,我們會感到很有趣,但在那之后,代碼開始變得糟糕起來。你會迷失在指令和作用域里,而且層間的雙向數(shù)據(jù)流會變得像蛋糕上的櫻桃一樣,那些新來的開發(fā)人員甚至都不想去觸碰一下,因?yàn)樗鼈兲y以管理了。
為什么會這樣?
Angular.js是在2009年出現(xiàn)的,那個(gè)時(shí)候前端世界還很單純,甚至沒有人會想到狀態(tài)問題。不過我們不能抱怨這些人,他們只是想創(chuàng)造出一個(gè)框架,并成為Backbone的競爭對手,他們賦予它一些新的概念,并且可以少敲一些代碼。
Angular 2
我們只是創(chuàng)建了hello world應(yīng)用,它就生成了很多相關(guān)文件。你需要使用Typescript和編譯器才能開始工作。這些對于我來說已經(jīng)夠了……在開始真正的開發(fā)工作之前,我還是覺得需要敲的代碼太多了。在我看來,Angular 2團(tuán)隊(duì)只是試圖構(gòu)建一個(gè)可以完美擊敗React的框架,而不是試圖為一般的用戶解決業(yè)務(wù)上的問題。或許是我想錯(cuò)了,或許我會改變想法。不過我還沒有太多使用Angular 2的經(jīng)驗(yàn),我們只是構(gòu)建了一個(gè)計(jì)算器演示應(yīng)用作為內(nèi)部的評估。Vue.js網(wǎng)站上有一個(gè)很精彩的關(guān)于Angular 2和Vue.js之間的比較,這兩者之間共享了很多設(shè)計(jì)上的概念。
Vue.js
簡而言之,Vue.js是一個(gè)我等待了很久的框架(我以后會討論Vue.js 2,相對第一個(gè)Vue版本,它做了很多改進(jìn),目前所說的是第一個(gè)穩(wěn)定版本)。對于我來說,它優(yōu)雅而簡潔,并把注意力集中在解決問題上。Vue.js是繼2007年jQuery出現(xiàn)之后JS世界最大的一次改變。
如果你看過Vue.js的熱度圖,你會發(fā)現(xiàn)不止我一個(gè)人這么認(rèn)為:http://www.timqian.com/star-history/#vuejs/vue&facebook/react。
Vue.js是2016年發(fā)展最快的JS框架之一,而且我認(rèn)為它的崛起并不是因?yàn)榉劢z的過度追捧,也不是因?yàn)槟硞€(gè)大公司的權(quán)威推動(dòng)。
Laravel把Vue.js加入到它的核心組件,這算得上是一件大事。
Vue.js的優(yōu)勢
Vue.js在可讀性、可維護(hù)性和趣味性之間做到了很好的平衡。Vue.js處在React和Angular 1之間,而且如果你有仔細(xì)看Vue的指南,就會發(fā)現(xiàn)Vue.js從其它框架借鑒了很多設(shè)計(jì)理念。
Vue.js從React那里借鑒了組件化、prop、單向數(shù)據(jù)流、性能、虛擬渲染,并意識到狀態(tài)管理的重要性。
Vue.js從Angular那里借鑒了模板,并賦予了更好的語法,以及雙向數(shù)據(jù)綁定(在單個(gè)組件里)。
從我們團(tuán)隊(duì)使用Vue.js的情況來看,Vue.js使用起來很簡單。它不強(qiáng)制使用某種編譯器,所以你完全可以在遺留代碼里使用Vue,并對之前亂糟糟的jQuery代碼進(jìn)行改造。
恰到好處的神奇
Vue.js可以很好地與HTML和JS一起協(xié)作。你可以開發(fā)出非常復(fù)雜的模板,而不會影響你對業(yè)務(wù)的專注,而且這些模板一般都具有很好的可讀性。當(dāng)模板膨脹到很大的時(shí)候,說明你在業(yè)務(wù)實(shí)現(xiàn)方面已經(jīng)取得進(jìn)展,這個(gè)時(shí)候你或許想把模板拆分成更小的組件。相比項(xiàng)目啟動(dòng)之初,此時(shí)你對應(yīng)用的整體“映像”會有更好的把握。
從我的經(jīng)驗(yàn)來看,這個(gè)跟在React里不太一樣:Vue.js幫我節(jié)省了很多時(shí)間。在React里,在一開始就要把組件拆分成微組件和微函數(shù),否則你會很容易迷失在亂糟糟的代碼里。在React里,你需要花很多時(shí)間在一次又一次的整理prop和重構(gòu)微組件(這些組件可能永遠(yuǎn)都不會被重用)上面,因?yàn)槿绻贿@么做,在修改應(yīng)用邏輯時(shí)就看不清方向。
在Vue里面使用表單是件輕而易舉的事情。這個(gè)時(shí)候雙向綁定就會派上用場。就算是在復(fù)雜的場景里也不會出現(xiàn)問題,不過watcher乍一看會讓人想起Angular 1。在你拆分組件的時(shí)候,會經(jīng)常用到單向數(shù)據(jù)流和回調(diào)傳遞。
如果你需要用到編譯器的一些特性、lint、PostCSS和ES6,你會如愿以償。在Vue.js 2里,Vue的擴(kuò)展特性將會成為開發(fā)公共組件的默認(rèn)方式。順便提一下,開箱即用的組件CSS看起來是個(gè)好東西,它們可以減少對CSS層級命名和BEM的依賴。
Vue.js的核心具有簡單有效的狀態(tài)和prop管理機(jī)制,它的data()和props()方法在實(shí)際當(dāng)中可以有效地工作。通過Vuex可以實(shí)現(xiàn)更好的關(guān)注點(diǎn)分離(在我看來它跟React里的Mobx有點(diǎn)類似,都包含了部分可變狀態(tài))。
我認(rèn)為大部分Vue.js場景都不需要Vuex提供的狀態(tài)管理,不過多一個(gè)選擇總不是壞事。
Vue.js的不足
最大的一個(gè)問題:模板的運(yùn)行時(shí)錯(cuò)誤描述不夠直觀,這個(gè)跟Angular 1有點(diǎn)類似。Vue.js為JS代碼提供了很多有用的警告信息,例如當(dāng)你試圖改變prop或不恰當(dāng)?shù)厥褂胐ata()方法時(shí),它會給出警告。這也是從React借鑒過來的比較好的方面。但對模板的運(yùn)行時(shí)錯(cuò)誤處理仍然是Vue的一個(gè)弱項(xiàng),它的異常堆棧信息總是指向Vue.js的內(nèi)部方法,不夠直觀。這個(gè)框架還很年輕,還沒有穩(wěn)定的社區(qū)組件。大部分組件是為Vue.js 1創(chuàng)建的,對于新手來說有時(shí)候難以區(qū)分它們的版本。不過你可以在不使用其它第三方庫的前提下在Vue里面完成很多事情,你可能需要一些ajax庫(如果你不關(guān)心同構(gòu)應(yīng)用,可以考慮vue-resource)或者vue-router,這在一定程度上平衡了Vue在組件方面存在的不足。社區(qū)軟件包里的代碼有很多中文注釋,這一點(diǎn)也不奇怪,因?yàn)閂ue.js在中國很流行(它的作者就是個(gè)中國人)。Vue.js是由一個(gè)人維護(hù)的項(xiàng)目,這個(gè)也算不上大問題,不過還是要考慮其它一些因素。尤雨溪是Vue的作者,他曾經(jīng)在Google和Meteor工作,在那之后他創(chuàng)建了Vue。Laravel也曾經(jīng)是一個(gè)單人項(xiàng)目,不過后來也很成功,但誰知道呢……在Drupal中使用Vue.js
首先聲明一下,我們不打算在Qwintry里使用Drupal 8,因?yàn)槲覀冋谵D(zhuǎn)向使用更快跟簡單的PHP和Node.js架構(gòu),況且我們的遺留代碼是基于Drupal 7。
因?yàn)槲覀兊倪z留系統(tǒng)qwintry.com是基于Drupal開發(fā)的,所以有必要在Drupal里對Vue.js進(jìn)行測試。對于遺留代碼,我并不引以為豪,不過它們能夠正常運(yùn)行,并為我們帶來價(jià)值,所以我們尊重它們,并對它們進(jìn)行改進(jìn),還增加了很多新的特性。以下列出了我們在Vue和Drupal里所做的事情:
就地編輯復(fù)雜的訂單節(jié)點(diǎn)對象。這個(gè)功能包括為客戶生成票據(jù)以及快速對商品進(jìn)行編輯。它要求提供基本的JSON API來加載和保存節(jié)點(diǎn)數(shù)據(jù),沒有什么特別的,就是一些菜單回調(diào)函數(shù)。
為我們正在使用的SaaS軟件系統(tǒng)提供了兩個(gè)基于REST的儀表盤,有了這兩個(gè)儀表盤,我們的客服就不需要登錄到不同的站點(diǎn)去檢查客戶相關(guān)信息,他們可以直接從儀表盤上看到這些。
我知道還有很多后端開發(fā)人員被困在Drupal 7的Ajax系統(tǒng)里,仍然停留在2010年的水平。
我知道,當(dāng)你試圖使用Drupal的核心特性來構(gòu)建多步Ajax交互表單時(shí),事情會變得多么復(fù)雜,代碼會變得多么的難以維護(hù)。是的,罪魁禍?zhǔn)拙褪莄tools_wizard_multistep_form()和Ajax render!
Drupal的開發(fā)人員同時(shí)面臨著構(gòu)建現(xiàn)代UI的挑戰(zhàn),但現(xiàn)代JS框架的復(fù)雜性又讓他們望而卻步。我在一年前就是這樣的。我可以告訴你,現(xiàn)在正是使用Vue.js來改善UI的最佳時(shí)機(jī),把Vue.js的代碼庫放到/sites/all/libraries里,通過drupal_add_js把它們添加到模板里,然后開始改造吧。你會驚奇地發(fā)現(xiàn),在客戶端(包括表單)使用Vue,會讓hook_menu里的JSON回調(diào)變得相當(dāng)易于維護(hù)。
在Yii 2里使用Vue.js
一個(gè)有趣的事實(shí):Yii是由一個(gè)叫作薛強(qiáng)的中國人創(chuàng)建的,所以Yii+Vue技術(shù)棧的發(fā)音并不難,它們是中國式的技術(shù)棧 :)
對于新版的Qwintry.com(還沒有公開發(fā)布),我們選擇Yii 2,我們相信它是最好也是最快的PHP框架之一。它當(dāng)然不如Laravel流行,但我們用得很開心(不過我們也研究過Laravel,他們確實(shí)做得不錯(cuò))。
我們將逐漸減少Yii 2和PHP生成的HTML代碼量,而在REST后端生成越來越多的JSON,這些JSON是為VueJS客戶端生成的。對于我們的Active Record模型,我們遵循的是API先行原則。
我們非常重視API,這也是為什么我們花了很多時(shí)間完善API文檔,雖然它們只是在內(nèi)部使用。
使用PHP 7和最新的MySQL數(shù)據(jù)庫,Yii 2 JSON后端的響應(yīng)速度幾乎跟Node.js沒有什么區(qū)別(差別也就是15到20毫秒),這對我們來說足夠了,更何況它比Drupal要快上10到20倍。這是一直以來PHP跟其它第三發(fā)庫最好的整合方式,足以保證我們手頭代碼的穩(wěn)定。
總的來說,Yii 2和Vue.js的結(jié)合所帶來的響應(yīng)速度是很快的,而且我們也很高興基于這兩個(gè)框架開發(fā)代碼。
我們還在內(nèi)部的很多項(xiàng)目里使用了Vue.js。
結(jié)論
我們在三個(gè)月的時(shí)間里使用Vue.js為不同的項(xiàng)目開發(fā)了很多代碼,結(jié)果也很令人滿意。三個(gè)月對于后端來說也許算不上什么,但在JS世界里,它舉足輕重 :) 我們將會關(guān)注后續(xù)的進(jìn)展。如果尤雨溪走對了方向的話,我期待著Vue會在16到24個(gè)月之后會變成主要的JS框架,至少對于小型的前端團(tuán)隊(duì)來說是這樣的。不過我認(rèn)為React仍然會是2017的主要JS框架,特別是如果React Native能夠以之前的速度改進(jìn)并成熟起來。