GitChat 做者:Meathill
原文:用 Vue 改造 Bootstrap,漸進提高項目框架
關注微信公衆號:「GitChat 技術雜談」 一本正經的講技術javascript
【不要錯過文末彩蛋】html
Vue 橫空出世,以迅雷不及掩耳之勢橫掃前端界,儼然有當年 jQuery 之勢。我認爲 Vue 成功的關鍵在於三點:前端
學習曲線平緩,有點經驗的前端基本上一天就能看完文檔,而後就能夠上手操做。vue
上升空間很大,組件化/路由/Vuex/Ajax,生態完整,架構強壯,用它構建中大型項目也很容易。java
API 設計優雅,而且和標準很友好。react
可是在我看來,不少 Vue UI 組件庫反倒走在一條錯誤的道路上:過度追大求全。好比說,第一個組件多半是 Grid,CSS 能搞定的事情爲何要作成組件?前端原本就是 HTML/CSS/JS 的集合,咱們理應把合適的技術用在合適的地方。jquery
固然,若是你是想把一切都塞進 JS 的「JS 至上黨」,這樣作也行。不過,不是每一個人都要從頭開始作一個新產品,不少同窗正在維護一個老產品,如何把新技術應用到老產品裏,讓它老樹開新花,也是本文的主旨。webpack
因此接下來,我會介紹這些內容:git
前端框架不少,爲何要使用 Vue?es6
一個基於 jQuery + Bootstrap 的後臺項目。
對其進行有限的改造,讓它能漸進地得到提高。
CSS 的歸 CSS,JS 的歸 JS。
初中級前端,但願學習 Vue 和組件式開發。
後端,用過 Bootstrap,想升級改造框架。
ES6 = ES2015
MVVM Model-View-ViewModel,一種現代化的 UI 架構體系,很是適合 HTML + CSS 這樣的標記型語言,經實踐證實能夠大大提高開發效率。本文中大部分和 Vue 互相指代。
響應式 在 CSS 領域,咱們能夠簡單理解成手機端和桌面端有不一樣的呈現;在 MVVM 框架(本文上,它指界面根據數據自動刷新顯示。
Vue 實現響應式的基礎是 ES5 中的 Object.defineProperty()
方法,它把普通的屬性讀寫改爲 getter/setter,以便注入其它操做。大部分現代化瀏覽器都已經支持這個方法,但一些古老的瀏覽器如 IE8 不支持(如今還在使用 IE8 的人你也很難期望他們會升級),若是你要使用 Vue,請確保你的項目不會跑在這些平臺上。
本文使用 Bootstrap 4.0.0-alpha.6,Vue 2.4.0+。
你們好,我叫翟路佳,花名「肉山」,這個名字跟 Dota 不要緊,從高中起伴隨我到如今。
我熱愛編程,喜歡學習,喜歡分享,從業十餘年,投入的比較多,學習積累到的也比較多,對前端方方面面都有所瞭解,但願能與你們分享。
我興趣愛比如較普遍,尤爲喜歡旅遊,歡迎你們相互交流。
你能夠在這裏找到我:
本書採用「保持署名—非商用」創意共享4.0許可證。
只要保持原做者署名和非商用,您能夠自由地閱讀、分享、修改本書。
若是您對於文中的內容有任何疑問,請在評論或 Issue 中告訴我。亦可發郵件給我:meathill[at]gmail.com。謝謝。
對前端框架不感興趣的同窗能夠跳過這一章,並不會影響後面的理解。
這章主要解決這些問題:
爲何要切換到 Vue 上?
jQuery 真的過期了麼?
除了 Vue 之外,我還有其它選擇麼?
我以前還作過一次視頻分享 jQuery, Backbone, Vue,也是經過分析 JS 開發的歷史,對比三個時期最具表明性的框架。感興趣的同窗能夠看一下。
有位名人說過:「一我的的命運啊,固然要靠自我奮鬥,可是也要考慮到歷史的行程。」這句話放在 Web 技術上,其實也很是正確。
發明 HTML 的目的是爲了方便閱讀文獻,因此它身上有不少印刷業的影子。後來你們嫌排版沒有其它工具作得漂亮,因而又發明了 CSS。結果憑藉着極高的傳播效率,市面上又缺乏競品,急速普及,被你們拿來幹各類事情。
在那個撥號上網的洪荒年代,瀏覽器還很是初級,與服務器進行數據交互的惟一方式就是提交表單。用戶填寫完成以後,交給服務器處理,若是內容合規固然好,若是不合規就麻煩了,必須打回來重填。因此很容易想象:當用戶填完100+選項,按下提交按鈕,等待幾十秒甚至幾分鐘以後,反饋回來的信息倒是:「您的用戶名不能包含大寫字母」,他會有多麼崩潰多麼想殺人。爲了提高用戶體驗,網景公司的布蘭登·艾克用了大約10天時間,開發出 JavaScript 的原型,今後,這門註定改變世界的語言就誕生了。
這個時候瀏覽器還處於很是嚴重的分裂狀態。IE 自恃有 Windows 護身,真是想怎麼搞怎麼搞。另外一邊的網景後來的 Firefox 空有一腔報國熱血,可是怎麼都幹不過 IE。後來乾脆全捐給開源社區,曲線救國,用免費開源的羣衆戰爭和微軟打。
這種環境受苦的仍是網頁開發者。同一個頁面,常常要寫兩套代碼,而後尋找各式各樣的 Hack 方法,費時費力。因而 jQuery 出現後你們以爲真是「春風十里,不如你」。
jQuery 的口號是:「Write less, do more「,爲 Web 開發作出了卓越的貢獻:
提供統一的操做接口。你不須要擔憂代碼跑在什麼瀏覽器上,自有 jQuery 來適配。
封裝了不少經常使用接口。好比增刪樣式,只須要 .addClass(className)
就能夠,比原始方法簡單不少。
用組合模式減小錯誤。HTML 結構多變,JS 操做 DOM 節點很容易發生錯誤,尤爲早期切頁面和寫 JS 的極可能不是同一我的。用 jQuery 你至少不用擔憂會報錯。
就這樣,jQuery 憑藉着出色的工程設計,俘獲了大量開發者的心,是如今最普及的框架。
jQuery 治下的 DOM 操做實在太簡單了,選擇器 + 修改,還不用擔憂報錯。這就比如說,鄰居有個熱心大哥,買了輛車,說你出門儘管找他。而後他還真的來者不拒,笑臉相迎,以致於小區裏的人出門都不開車也不打車也不騎車,都坐他的車。
開始老是好的,但隨着環境變化,老的優點也可能會變成問題。就像一個小區只有一輛車確定不夠同樣,開發者對 DOM 操做過度依賴,會致使 JS 代碼和 HTML 嚴重耦合,牽一髮而動全身,維護成本大幅升高。
另外,通過幾年發展,包括 Flash 等軟件不懈探索,此時的 Web 已經不只僅要提供能閱讀的圖文信息,愈來愈多的 RIA(Rich Internet Application,富互聯網應用) 涌現出來,你們都在竭盡所能地把桌面軟件搬上互聯網。這對前端開發提出了更高的要求。
Backbone 給出了本身的答案。它是一個 MVP 框架,充分利用了現代 Web 技術,包括 jQuery。它大大減小了操做 DOM 的需求,轉而教你們使用模板。它以數據爲視角來組織代碼,告訴你們:原來 JS 還能夠這麼寫。
使用 Backbone 編寫的應用在工程性方面提高巨大,維護成本大大下降,後期增刪改功能都變得相對容易。開發中大型軟件也變得可行,開發者感覺到技術架構帶來的價值,更加主動的放眼看世界,果真還有更值得咱們學習的東西。
MVC 是 Smalltalk 提出的編程模型,實際上,那個時期的概念跟今天相距甚遠:主要輸入設備是鍵盤,鼠標指針在屏幕上移動都須要開發者本身寫,更沒有「元素-點擊事件」這種極其抽象的東西。因此它裏面的 Model-View-Controller 不能用如今的概念去套。(感興趣的同窗能夠去看下擴展閱讀裏頭兩篇文章。)
隨着 UI 技術的發展,接下來出現的是 MVP 模型。它裏面的 P(Presenter)已是 UI 控件了,因此和 Web 技術很是接近。Backbone 就是這樣架構的框架。
接下來,UI 技術進一步發展,HTML 這種標記語言如日中天,因而微軟最先提出了 MVVM 的概念,而且將它應用在自家產品中。它的模式是這樣的:
(圖片來源: https://erazerbrecht.wordpress.com/2015/10/13/mvvm-entityframework/)
View 視圖和 ViewModel 雙向數據綁定,View 既是數據展現窗口,也是接受用戶操做的輸入來源。ViewModel 除了負責渲染邏輯之外,還負責全局數據管理,以及和真正的數據源 Model 進行交互。MVVM 很是吸引人,由於它在 MVP 的基礎上又進行了一次抽象,而且提供雙向數據綁定,因此開發維護效率巨高,代碼量可能只有 MVP 的 1/10,但功能一致甚至更強。
最先在 Web 中引入這套模式的是 Knockout。用英語的句式來講:它是如此之早以致於它甚至支持 IE 6 和 Firefox 3.5……因而這也讓它背上了沉重的歷史包袱:
它必須顯式的聲明數據綁定,顯式到語法囉哩吧嗦。
賦值時必須使用 .get(key)
.set(key, val)
這種語法,甚至 a.get('key1').get('key2').get('key3').set('key4', val)
這樣。
另外一個嘗試來自 Angular1。它不須要這麼複雜的語法,而是採用「髒查詢」的方式,即當某個可能致使頁面從新渲染的操做產生後,檢查全部備案過的變量,若是有改變的,就從新渲染。這種作法的問題就是慢,在運算能力充足的桌面電腦上感受不明顯,可是在移動端就會很慢。桌面端也會慢,因此它只能追蹤1000個變量。
可是它的開發效率的確很高,在企業級佔據霸主地位,幾乎已是事實標準。
這兩個框架有些生不逢時,由於瀏覽器總體環境的限制,它們選擇了不夠完善的實現方案。可是 Angular 至少證實了這個方向可行,並且效果很好,因而他們註定要被後浪拍在沙灘上。
接下來便輪到咱們的主角登場了。不過與它一塊兒登場的還有另外兩名選手,都是背景深厚實力不凡的大 Boss,我先介紹它們。
React 是 Facebook 研發的框架。它最大的特色是使用虛擬 DOM 做爲實際 DOM 的影子,這樣當它判斷是否須要從新渲染時,就不用費力的與 DOM 交互,而只要從 JS 內存中取出虛擬 DOM 作 diff 便可。這樣作能夠大大提高檢查效率,提升渲染速度。
但它本質上仍然在執行髒檢查,因此實際效率不過高,或者說,有明顯的天花板;只是面對愈來愈強的計算能力,這些損失浪費的表現愈來愈不明顯。可是虛擬 DOM 也帶來另一個好處:若是重寫它的實現機制,能夠在任意場合實現任意類型的渲染,包括移動 App。因而便誕生了 React Native 這個項目,能夠直接把基於 React 開發的 Web 項目轉譯成原生應用。
React 背靠 Facebook 這座大山,國內也有阿里支持,社區異常活躍,生態異常豐富。又能編譯出原生應用,這是它最大的優點。
不過 React 也有缺點,致使我沒有選擇它:
要使用 React 必須使用醜陋的 JSX,我認爲這是反標準的。
它的生態雖然豐富,可是官方並無引導,沒有全家桶,各類實現良莠不齊。
設計上爲照顧大規模企業級開發,裏面有衆多新概念,學習曲線很是陡峭。
夾帶私貨,現在 Apache 基金會已經禁止使用 React。
Angular 吸收了第一代的教訓,在 Angular 2 裏重寫了底層邏輯,也使用虛擬 DOM 來判斷是否須要從新渲染。因此 React 的優點它基本也有。以後的 v四、v5都是在 v2 基礎上的改進,並無特別大的變化。
Angular 的學習曲線就更陡峭了,應該說它從一開始就沒打算走羣衆路線,而是直接奔着大而全的企業級開發框架去作的(由於 v1 就是這樣作的而且取得了成功)——相對來講 React 表現的還有點扭捏。
另外 Angular 爲了可以更好的對接大規模團隊開發,選擇 TypeScript 做爲開發語言,一方面使得學習成本更高;另外一方面也和標準化漸行漸遠。這也是我不推薦它的緣由。
與前面兩個競品不一樣,Vue 是我的做品,並且在 2013 年纔開始開發。但我選擇它推薦它並非由於什麼挑戰大公司霸權的情懷,而是它的確設計得很棒。
雙向綁定效率高
問世的晚,歷史包袱就少。Vue 使用 ES5 新增的 Object.defineProperty()
方法,將對象的屬性轉化爲 getter/setter
,這樣咱們習覺得常的 this.a=1
賦值語句實際上就被改寫成 this.set('a', 1)
,而這個操做對開發者來講是徹底無感的!這樣咱們一方面能夠正常寫代碼,另外一方面還能夠輕鬆的享受到雙向綁定,而且是高效的沒有多餘動做的綁定,任何有點點潔癖或者強迫症的人都會以爲很舒服吧!
模塊化減緩學習曲線
初入門時,咱們只須要學習 Vue 就好。若是隻想實現簡單的「數據<=>視圖」映射,區區幾行代碼就足夠了,很是簡單。甚至能夠直接拿來替換 jQuery。
以後隨着項目增大,使用加深,能夠慢慢的開始使用組件,使用路由,使用全局狀態管理。這一系列進化都在平緩的進行。
貼近標準
與大公司喜歡夾帶私貨,搞自有標準不一樣,Vue 是標準友好的。甚至連 Vue 控件,寫出來都跟普通 HTML 同樣。做爲我的開發者,或者小公司開發人員,我不肯意介入大公司之間的角逐,我見過妖魔橫行的年代,我但願標準一統天下。
在表明先進生產力的 MVVM 框架裏,我最終選了 Vue 做爲新的主攻框架。我也把它推薦給你們。
框架入門
這一章會簡單介紹 Bootstrap 和 Vue 的入門知識,若是您已經瞭解,能夠跳到下一章。
Bootstrap 簡介
Bootstrap 是 Twitter 的兩位前端工程師搞出來的前端框架。它包含了大量 UI 組件,能夠覆蓋到不少開發場景,大大提高開發效率。
Bootstrap 通過幾輪升級,如今處於 v4-alpha.6 階段,做爲一個免費開源產品,原做者太忙,alpha 快兩年了,還沒 beta,不知道何時能正式版。不過通過我一段時間的使用,我以爲目前這個版本基本上沒啥大問題,仍是推薦你們使用。
做爲最流行的前端框架,有不少人基於它作出了不少值得學習的項目,不少都是開源免費的,也一併推薦給你們:
CoreUI 基於 Bootstrap 作的後臺類模板,免費,有多種框架配置
Start Bootstrap 另外一套免費模板
Bootstrap 的文檔很是詳細,我就不湊字數了。未來文章發佈後如有問題再補充吧。
Vue 入門
Vue 的文檔很是棒,尤爲還有中文版,學習起來輕鬆愉快。
這裏我也再也不重複官方已有的內容了,你們本身看就好。說一下我理解中,從 jQuery 向 Vue 轉換時須要注意的東西。
jQuery 裏幾乎沒有數據抽象。你面對的就是一個雖然錯綜複雜,可是總能找到聯繫的 DOM 樹,只要你有耐心,總能把它改爲你想要的樣子。
MVP 就抽象出數據層和視圖層,可是還要咱們手動更新視圖;MVVM 比 MVP 的抽象更進一步,只要操做數據。因此咱們必需要理解它的抽象,而且習慣它的抽象。
在這個體系裏,咱們應避免直接操做 DOM,由於一切都是數據的映射。舉個例子,一個新聞列表,在傳統開發模式中,是無數個 DOM 操做的結果;而在 Vue 裏,就是經過模板把數據映射成 HTML。對後臺類產品而言,這很好理解也很好實現,由於後臺能夠抽象成用戶與數據的交互,而後還原成數據的展現和修改,繼而直接對應到屏幕上的組件上。
在寫 Vue 應用的時候,咱們須要注意,哪些數據是業務數據,即要拿來跟後端數據進行交互的;哪些數據是界面數據,即用來切換頁面狀態,和業務無關。可是基本上,咱們不須要直接操做 DOM。
組件是 Vue 最值得注意的強大特性。組件化和組件複用將大大提高咱們的開發效率。
使用組件主要有兩種方式:
註冊全局組件。這種方式很簡單,有點相似 jQuery 插件,咱們只要引用組件就好,而後就能夠在模板中使用特定的組件標籤。比較適合已有項目,能夠在不怎麼改動的前提下接入應用 Vue。
使用局部組件。這種方式要複雜一些,並且也有幾種不一樣的實現,若是同時要加載組件模板和組件樣式,可能還要用 webpack + vue-loader。
由於這篇文章就是「漸進式改造項目」,因此根據項目現狀選擇合適的方法頗有必要。
這個其實不是 jQuery 和 Vue 的差異,只是在眼下這個時間點,ES6 已經實裝到絕大部分瀏覽器裏,因此咱們不管是看文檔、看教程都會看到大量 ES6 的內容。至於整個前端生態,基於 Node.js 開發的各類工具也已經普及到方方面面,使用 webpack + 各類 loader 已經成了默認功課。
因此,那個用 <script src="/path/to/jquery.js">
引入 jQuery,而後就能夠在頁面當中任意使用 jQuery 相關技術的年代,其實已通過去了。
目標改造項目簡介
這個項目,原本有機會成爲一個真實的項目,但造化弄人,因此它如今只是一個虛擬的項目。
這個項目是這樣的:
一個後臺產品,主要面向公司內部員工,提供對公司產品的維護。
後臺已經工做了5年,如今仍在正常工做,不能下線。
後臺不斷有新需求,不太可能另起爐竈開發。
後臺使用 Bootstrap + jQuery 搭建,是一個先後端分離結構,每一個子頁面加載完成後,會有一個組件管理器對子頁面進行掃描,初始化組件,而且完成數據加載。
公司的角度,對 Vue 即不支持也不反對,只要求必須保證開發效率和工做效率。
雖然這個產品很重要,可是公司不會投入更多的人力,從頭至尾都只有原先的幾我的來作。
如今,做爲一個有追求有夢想,而且想在跳槽的時候有更多加分的前端開發者,我要對它進行 Vuerify 改造了!
第0步:分析
好的,囉嗦半天半天終於進入實戰了。不過,在動手以前我還要多說兩句。所謂謀定然後動,想清楚再動手,確定比頭腦一熱上去猛幹要好。
一個項目能跑5年,說明其設計大致上仍是過硬的。因此一開始最好不要動其根本,先找比較獨立的一起來弄最爲合適。
咱們看一眼 Bootstrap 的文檔,首先 佈局(Layout) 和 內容(Content) 的部分就不須要組件化了,CSS 搞得定的事情就不勞 JS 動手了。
組件(Component)裏面,有不少也是純 CSS(好比Badge) 或者能夠進行純 CSS 改造的(好比 Navs),這些咱們先無論,即便改造也等未來用 CSS 改造或者直接放到大組件裏。還有像 Tooltip 這樣,雖然帶着一點 JS,但實際上對 JS 的需求很低,咱們也不用管它,只要 jQuery 一日不死,這些舊臣都能盡忠報國。
看來看去,最值得拿來入手的應該是 彈窗(Modal) 和 通知(Alert) 之類和主要業務邏輯關係不大,自身可定製性比較強(JS 比較複雜)的組件。
好的,就拿彈窗來試刀吧!
篇幅所限,我無法展現對每個組件的重構/改造,因此本文中的幾個篇章都有特定的目的,主要在於介紹一門或幾個技術。但願你們觸類旁通,將它們應用到平常工做中。
本章的目的在於領你們入門。
彈窗除了用做通知之外,在後臺產品當中還常常用來對特定屬性進行修改,以下圖所示:
對於這樣的需求,在以前的產品中我是這麼作的,首先,HTML 部分就按照 Bootstrap 的標準套路來寫:
<div class="modal fade" id="sales-editor"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">修改職位</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <form action="/api/sales/" data-action="/api/sales/{{id}}" method="POST" id="sales-editor-form"> <input type="hidden" name="user_id" value=""> <div class="form-group"> <label for="type">職位</label> <select class="form-control" name="type" id="type"> <option value="1">區域總監</option> <option value="2">商務經理</option> <option value="3">商務助理</option> </select> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" form="sales-editor-form"><i class="fa fa-check"></i> 保存</button> <button type="button" class="btn btn-secondary" data-dismiss="modal"><i class="fa fa-times"></i> 取消</button> </div> </div> </div> </div>
表格模板是這樣的(只寫單行吧,其它部分意義不大):
<tr> <td>{{name}}</td> <td><button type="button" class="btn btn-primary edit-button" data-id="{{id}}">編輯</button></td> </tr>
這兩部分沒什麼好解釋的。接下來 JS 的部分就稍顯囉嗦:
// 經過後端接口取出來全部商務數據 let allSales = fetchAllSales(); // 編輯按鈕的點擊事件,每次點擊,除了打開新窗口,還要把表單裏面的幾個值修改一下 $('#table').on('click', '.edit-button', event => { let id = $(this).data('id'); let sales = allSales[id]; $('#sales-editor').modal('show') .find('[name=user_id]').val(id) .end().find('form').attr(function () { return $(this).data('action').replace('{{id}}', id); }) .end().find('select').val(sales.type); });
很明顯,正如前文所寫的那樣,jQuery 用起來很方便,能夠在 DOM 節點之間自由穿梭。可是由於它沒有抽象,因此任何操做都要手工編寫,會形成 JS 和 HTML 深度耦合,不只不方便維護,新功能開發的時候也要寫不少多餘的代碼。
接下來咱們要用 Vue 實現一樣地功能,應該怎麼寫呢?
首先咱們能夠觀察一下 Bootstrap 裏 Modal 的實現機制,很明顯,它是經過修改 .modal
元素的 display
屬性和增刪 show
樣式來實現的。那很簡單,咱們知道,Vue 裏,能夠經過 :prop="someValue"
把 vm 的值 someValue
綁定到 DOM 節點的 prop
屬性上,因此控制彈窗的打開關閉就很簡單:
<div class="modal fade" :class="isShow ? 'show' : ''" :style="isShow ? 'display:block' : ''" id="sales-editor" @click.self="close"> <!-- 內容 --> </div> <div class="modal-backdrop fade" :class="isShow ? 'show' : ''" v-show="isShow"></div>
let app = new Vue({ el: '#app', data: { isShow: false }, methods: { show() { this.isShow = true; }, close() { this.isShow = false; } } });
(這裏只列出重點代碼,其它由於不影響大局就沒有寫。具體能夠參考 codepen oegbvK)
這樣修改了以後,我發現打開關閉已經正常工做了,可是沒有動畫,看起來不夠炫酷。不要緊,咱們審查元素,發現是 .show
樣式增長的過渡效果,opacity 0 <-> 1,而同時設置 display
屬性會使得它缺失過渡效果。那很簡單,咱們能夠用 watch
觀察選項,偵聽 isShow
的變化,在它變化後,間隔一點點時間,再觸發動畫。關閉的動畫就要偵聽 transitionend
事件了,在關閉動畫完成後再隱藏這部分元素。修改後的代碼是這樣的:
<div class="modal fade" :class="isShowClass ? 'show' : ''" :style="isShow ? 'display:block' : ''" id="sales-editor" @click.self="close" @transitionend="hide"> <!-- 內容 --> </div> <div class="modal-backdrop fade" :class="isShowClass ? 'show' : ''" v-show="isShow"></div>
let app = new Vue({ el: '#app', data: { isShow: false, isShowClass: false }, methods: { show() { this.isShow = true; }, hide() { this.isShow = false; }, close() { this.isShowClass = false; } }, watch: { isShow(value) { setTimeout(() => { this.nextShow = value; }, 50); } } });
進展的還算順利,咱們如今已經能夠正常開關彈窗了。
接下來我要把表單中的其它屬性填進去。對於 Vue 來講,這就太簡單了,由於 MVVM 框架最擅長的就是把數據和 UI 進行雙向綁定,只用很是少的代碼。
<form :action="'/api/sales/' + id" method="POST" id="sales-editor-form"> <input type="hidden" name="user_id" :value="id"> <div class="form-group"> <label for="type">職位</label> <select class="form-control" name="type" id="type" v-modal="type"> <option value="1">區域總監</option> <option value="2">商務經理</option> <option value="3">商務助理</option> </select> </div> </form>
三兩下清潔溜溜。接下來改一下編輯按鈕的響應事件:
let modal = new Vue(....); // 把 Vue 組件弄出來備好 $('#table').on('click', '.edit-button', event => { let id = $(this).data('id'); let sales = allSales[id]; modal.id = id; modal.type = sales.type; modal.isShow = true; });
代碼量一下少了好多,真是舒爽!
不過還沒完,由於還要把數據提交給服務器,而且適當的顯示結果,因此咱們繼續工做。此次要偵聽表單的提交事件,把它攔截下來,將數據用 Ajax 的方式提交,而且顯示結果。
<form @submit.prevent="submit"> <div class="alert" :class="result-status" v-if="result !== null">{{result}}</div>
// Vue Modal 組件 data: { result: null, message: '' } methods: { submit(event) { $.ajax(event.target.action, { dataType: 'json', data: { type: this.type } }) .then( response => { this.result = response.code; this.message = response.message; this.$emit('saved', this.id, this.type); }) .catch( err => { this.result = 500; this.message = err.message; }); } } // 外面的老列表代碼 let allSales = fetchAllSales(); let modal = new Vue(....); modal.on('saved', (id, type) => { allSales[id].type = type; });
在這段代碼中,我偵聽到 submit
事件,而後把用戶選擇的數據經過 Ajax 上傳給服務器。當服務器返回時,再根據返回值,直接在窗口中顯示成功或失敗提示。接下來,將完成操做的消息廣播出去。外界接到消息後,能夠進行下一步的處理。
這裏你們能夠看到我在偵聽事件的 @submit
後面增長了 .prevent
,若是你留心的話,上一節還有用到 @click.self
,這可不是 jQuery 的事件命名空間,而是 Vue 的事件修飾符。這是一種語法糖,由於咱們常常須要在事件處理函數裏進行 .preventDefault()
之類的操做,Vue 乾脆把它們進行封裝,並以顯式聲明的方式提供給咱們。
另外,我在 Ajax 請求當中使用了 Promise 的寫法,若是你對 JS 的異步開發還不甚瞭解,推薦你看個人另外一篇文章:JavaScript 開發全攻略
初戰告捷!
在這一節中,咱們對一段老的,依賴 jQuery 和 Bootstrap 的代碼進行了重構,使得它如今支持 Vue,並以 Vue 的方式打開關閉以及與服務器交互。
可能會有同窗問:這樣作,是否是太簡單了……
沒錯,千里之行始於足下。若是咱們從頭開發一個項目,大能夠直接搬來一套 UI 框架,好比 Element.ui,而後全都按照他的標準來寫。但當咱們面對着一個已經上線提供服務的產品,就不得不謹慎行事,經過一點一滴的積累,把它逐步往咱們但願的目標改造。
不過你們能夠放心:
這個改造只是入門,咱們不會就此收手。
「MVVM 最早進 UI 架構」的優點還會保持很長時間,甚至在新的交互方式(好比語言,讀心)普及以前不會被取代,咱們大能夠把這個改造過程拉長到3~6個月,甚至一年,而不用擔憂半路又要改到其它方向。
當咱們學會並施行組件化開發,開發效率會大增。
本章的目的在於教授你們 Vuex 的用法,以及逐步用 Vue 接管全局。
一段時間過去,我用這種看起來很土的方式改造了一部分代碼,如今我但願更進一步,正好最近幾個需求都處在驗收階段,我有一些時間能夠作想作的事。
那就改造用戶系統吧!
之因此選擇這個方向,是考慮到用戶登陸與用戶信息管理是另外一個全局功能,和具體業務無關,但又在程序入口,經過對它的改造,咱們有機會去觸動更基礎的部分,對未來完全轉向 Vue 有很大幫助。
說幹就幹,我翻查老代碼,發現如今的處理方式,是在頁面初始化的時候,向服務器發起一個請求,驗證用戶身份。
若是已經登陸,獲取用戶的信息,好比名字,能夠看到的導航菜單等。
若是沒有登陸,則跳到登陸頁。
這個過程其實還有點複雜,複雜就複雜在須要根據用戶身份更新導航菜單。不過也正和我意,總得從外圍向內核滲透麼不是。
首先,修改整個 index.html,把左側導航列表改爲 Vue 模板:
<ul id="navbar-side-inner" class="nav side-nav"> <li class="nav-item"> <a href="#/dashboard"><i class="fa fa-dashboard"></i><span>首頁</span></a> </li> <li class="sidebar-nav-item" :class="invisible ? '' : 'hidden'" :id="'parent-' + item.link + item.subId" class="nav-item" v-for="item in sidebar"> <template v-if="item.sub"> <a href="javascript:void(0);" data-parent="#navbar-side-inner" data-toggle="collapse" class="accordion-toggle" :data-target="'#' + item.sub-id" v-if="item.sub"> <i class="fa" :class="'fa-' + item.icon" v-if="item.icon"></i> <span>{{item.title}}</span> <span class="caret" v-if="item.sub"></span> </a> <ul class="nav collapse" :id="item.subId"> <li :id="item.link" :class="invisible ? '' : 'hidden'" v-for="item in sub"> <a :href="item.link"> <i class="fa" :class="'fa-' + item.icon" v-if="item.icon"></i> <span>{{item.title}}</span> <button type="button" class="eye-edit-button" v-if="editing"><i class="fa fa-eye"></i></button> </a> </li> {{/each}} </ul> </template> <a :href="item.link" v-else> <i class="fa" :class="'fa-' + item.icon" v-if="item.icon"></i> <span>{{item.title}}</span> <button type="button" class="eye-edit-button" v-if="editing" v-if="editing"><i class="fa fa-eye"></i></button> </a> </li> </ul>
若是你仔細看上面這段的代碼,你會發現,它最多支持二級菜單,而且一二級菜單的 HTML 結構是徹底一致的,這裏爲接下來組件一章埋下了伏筆。不過這一章咱們姑且放過它。
而後把右上角的用戶身份部分也改爲 Vue 模板:
<li class="nav-item dropdown me"> <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> <span class="username">{{username}}</span> <span class="caret"></span> </a> <div class="dropdown-menu" role="menu"> <a href="#/my/profile/" class="dropdown-item"><i class="fa fa-user fa-fw"></i> 個人帳戶</a></li> <a href="#/my/finance/" class="dropdown-item"><i class="fa fa-yen fa-fw"></i> 財務管理</a> <a href="page/my/changepwd.html" class="popup" title="修改密碼" data-confirm="保存" data-cancel="取消"><i class="fa fa-fw fa-lock"></i> 修改密碼</a> <a href="#/my/settings"><i class="fa fa-cog fa-fw"></i> 設置</a> <div class="divider"></div> <a href="#/user/logout"><i class="fa fa-fw fa-sign-out"></i> 退出</a> </ul> </li>
模板告一段落。不管使用何種框架,保持對與 DOM 的解耦是很重要的。作的好的話,這個時候就能提現出效果了,此時咱們直接用一個大的 Vue 實例去控制整個後臺 UI,與原先的框架進行雙軌制管理。
// 原先的框架 var me = new tp.model.DIYUser() var profile = new tp.view.Me({ el: '.me', model: me }); var body = new tp.view.Body({ el: 'body', model: me }); var sidebarEditor = new tp.view.SidebarEditor({ el: '#navbar-side', model: me }); var ranger = new tp.component.DateRanger({ el: '.date-range' }); var search = new tp.view.Search({ el: '.global-search' }); // 新的框架 let app = new App({ el: 'body' });
由於用戶有關的部分都準備交給 Vue 處理,因此上面的 profile
和 sidebarEditor
都要幹掉。還好裏面都是處理界面邏輯的代碼,幹掉它們沒什麼大礙,最後就變成:
// 原先的框架 var body = new tp.view.Body({ el: 'body', model: me }); var ranger = new tp.component.DateRanger({ el: '.date-range' }); var search = new tp.view.Search({ el: '.global-search' }); // 新的框架 let app = new App({ el: 'body', data: { sidebar: null, me: null } });
還記得我前面對比 React 時說過的話麼?React 生態雖然豐富,但缺乏官方指定庫,因此質量良莠不齊。Vue 在這方面作的比較好,官方有幾個推薦產品組成全家桶。
雖然如今的改造東一榔頭西一棒槌,但放長遠來看,這套系統必定會總體遷移到 Vue,而且實現組件化。因此,爲了能在組件間共享狀態,我決定從如今開始就使用 Vuex。
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
若是你想了解 Vuex,建議去看官方文檔,很是短小精悍,不超過1個小時你就能看完。
上一章裏,我爲老框架創建了一個接班人,準備逐步把工做交給它。接下來,我就會用 Vuex 構建一個全局數據中心,方便未來在系統內部存取數據。此次爲了擴展考慮,我使用了 Vuex 的模塊概念,即把不一樣的數據放在不一樣的模塊裏,方便管理。另外,從如今起,我會肆無忌憚地使用新技術,使用 Vue 開發的功能會經過 Webpack 打包到 JS 裏(不包含原先的內容),加載到頁面中。
// index.js import Vue from 'vue'; import Vuex from 'vuex'; import { API } from 'config'; import user from './user/'; import mutations from './mutations'; import actions from './actions'; Vue.use(Vuex); export default { state: { API: API, _token: '', }, mutations, modules: { user } }; // user.js import mutations from './mutations'; export default { state: { id: 0, name: '', fullname: '', email: '', sidebar: null }, mutations }
而後回到入口 JS,在裏面添加用戶身份檢查:
import base from './store/'; let store = new Vuex.Store(base); let app = new Vue({ el: 'body', store }); let headers = new Headers(); headers.append('Accept', 'application/json'); fetch(API + 'login', { mode: 'cors', headers: headers, credentials: 'include' }) .then( response => { return [response.status, response.json()]; }) .then( (status, json) => { if (status === 401) { router.push('/login'); return; } store.commit(UserMutations.SET_USER_INFO, json.user); }) .catch( err => { router.push('/error/500'); });
這裏我用了 Fetch API,從服務器請求用戶身份。若是服務器返回 401 錯誤,就說明用戶沒有登陸,而後就經過路由跳轉到登陸界面;不然的話,把用戶信息記入 store
完成操做。
用戶登陸狀態也是後臺產品比較關心的內容。由於後臺有很嚴格的權限控制,不一樣權限的人看到的內容是不一樣的,甚至連菜單都是不一樣的。用戶登陸狀態都保存在服務器,前端是不知道的,因此,極可能用戶登陸已經超時了,可是前端框架仍然傻乎乎的向後臺發起請求。這個時候,後臺必須能從服務器端返回的信息裏看到端倪,並作出正確的處理,好比跳轉到登陸頁。
之前用 jQuery,咱們能夠用全局的響應函數 .ajaxError()
來處理。現在咱們正在作去 jQuery 化,因此這個工做也同樣移交給 Vue 一族來處理。
Vue 生態裏有個遠程資源組件,叫作 Vue Resouce,能夠用來取代 jQuery.ajax。因而我把它也加入項目中,而且利用它的中間件來作全局登陸狀態檢查:
Vue.use(VueResource);
Vue.http.interceptors.push( (request, next) => {
// 全部的頭 request.headers.set('Accept', 'application/json'); request.credentials = true; next( response => { if (response.status === 401 || (!response.ok && response.status === 0)) { router.push({ name: 'Login', params: { fetch: true, redirectTo: location.hash.substr(1) } }); } }); });
不過須要注意的是,只有經過 Vue-Resource 發起的請求才會被中間件過濾,因此其它由 jQuery 發起的請求仍然繼續用之前的策略處理。
組件是 Vue 最重要的特性。簡單來講,任何 Vue 實例均可以視做組件;一個組件能夠由多個組件組合而成;因此一個應用,不管規模,均可以視做若干個組件組成的總體。
爲了接下來的內容,我建議你先讀完 Vue 組件的文檔。
組件的目的是提升代碼的複用性。舉個例子,前面咱們實現了 Modal 組件的 Vue 化,彷佛蠻簡單的,效果還不錯。可是當另外一個頁面當中也須要這樣的 Modal,麻煩就來了。徹底複製一遍固然能夠,但這明顯不是最好的作法。
這種時候,咱們就該把它作成組件。由於整個產品框架仍然處於混合運行的狀態,因此單文件組件 暫時還不容易接入,咱們仍是選擇全局組件吧。
Vue.component('my-modal', { template: '', props: { isShow: { type: Boolean, default: false } }, data() { return { isShowClass: false, result: null, message: '' } } methods: { submit(event) { // 提交 }, show() { this.isShow = true; }, hide() { this.isShow = false; }, close() { this.isShowClass = false; } }, watch: { isShow(value) { setTimeout(() => { this.nextShow = value; }, 50); } } })
爲防止你沒有仔細閱讀文檔,我仍是把一些要點提一下。首先,註冊全局組件要用 Vue.component
方法,註冊出來的組件經過在 HTML 裏寫標籤來使用,如:
<my-modal> </my-modal>
其次,組件的 data
屬性必須是一個函數,而後返回一個包含要用到數據的對象。
最後,任何組件在用的時候都會變成「子組件」,因此這個時候向它傳值必須經過 prop
屬性。在咱們的例子中,負責控制 Modal 顯示/隱藏的屬性是 isShow
,原先經過 modal.isShow = true;
便可將它彈出。現在咱們須要把它暴露給父組件,因此放在 props
裏。
接下來,我還要調整一下它的模板。既然是組件,必須考慮複用,因此內部表單確定不能寫死了。Vue 給咱們提供了 Slot(插槽)做爲插入外部內容的手段,因此模板能夠改爲這樣:
<!-- 前面不變 --> <div class="modal-body"> <slot @submit.prevent="submit"></slot> </div> <!-- 後面也不變 -->
模板能夠放在任何地方,好比統一堆在 index.html 裏面,用 <script type="text/x-template"></script>
包裹;也能夠寫在組件的 template
屬性裏,只要能訪問到,都不是問題。
這樣,咱們就能夠在別的地方使用這個 Modal 組件了:
<div class="隨便什麼容器" id="some-vue-app"> <my-modal @saved="onModalSaved"> <form action="/api/some/" method="post"> <!-- 表單內容 --> </form> </my-modal> </div>
不過使用 Vue 組件的必定是其它 Vue 實例,若是要混合使用的話,也要用 Vue 實例做爲中介。
單文件組件是更好的選擇。它更容易被複用、被修改、被測試。
不過單文件組件更依賴對整個前端工具體系的掌握,你必須會用 Webpack,會配置各類 loader,對於一些初學者可能會比較困難。因此我建議不要着急上單文件組件,幹什麼事都應該按部就班,先把 Vue 用好用熟練,解決掉平常用到的問題,再找機會切換到單文件模式就好。
以前的工做固然不是白費的,Vue 的單文件組件也能夠正常使用 import、export,因此以前寫好的組件能夠直接放進來。
好比咱們的 Modal,完成以後,未來若是要謄到單文件組件中,只須要在裏面引用就好:
<template> .... </template> <script src="./my-modal.js"></script>
改造路由自己其實並不困難。真正的難點在於,以前的路由,不管是基於 Backbone.Router 仍是基於 Page.js,都是偵聽 URL 變化,而後調用回調函數來處理。而 Vue 官方插件 Vue-Router,則是直接實例化頁面組件。
這樣致使咱們很難漸進式的遷移功能頁,必須當心翼翼的把兩個路由分開,好比 /new/path/to/feature/
交給新路由,其它的交給老路由,等未來完全遷移重構完畢再把 new
去掉。
自己路由的寫法直接看官方文檔就行了,大約1個小時就能讀完,這裏就不在贅述。
通過不懈的努力,後臺項目的改造告一段落。大部分組件都被重構成基於 Vue,有些還被很好的重構成可複用的組件,用在其它項目中。現在,雖然用的仍是 Bootstrap,但已經能夠把 jQuery 從依賴中拿掉了。
整個系統基於 Vue 全家桶開發,使用 Webpack + Babel 管理,既時髦又高效。咱們對 Vue 開發也很熟悉了,平常開發不在話下。
不過從這個時刻起,咱們也不須要像之前那樣謹小慎微,土啦吧唧的以「夠用就行」的標準來寫組件。項目重構完成以後,由於 Vue 單文件組件出色的解耦特性,引用外部組件庫也是個不錯的選擇。並且從提高技術的角度,我仍是強烈推薦你們用一用別人的框架。
我這方面的經驗也很少,就推薦兩個吧:
Element UI 由餓了麼團隊開發維護的組件庫。
iView 一套基於 Vue.js 的高質量 UI 組件庫。
推薦這兩款組件庫的緣由除了它們自己質量不錯,都是國人開發,中文文檔豐富也是重要緣由。
其實,動相似的腦筋的人,我確定不是第一個。好比 VueStrap 和 VueStrap 這兩個項目,可是它代碼寫的實在太差,功能也不完備,用起來各類不爽,因此我乾脆重寫了一些。
Vue 比較讓我欣賞的一點也是如此:實現組件庫很簡單,以爲別的庫不合適,本身搞也很快。
甚至我認爲這纔是正道:大部分基礎組件,能夠依賴 CSS 來實現,又簡單又可靠;部分複雜的功能,本身實現有針對性而且能全把握的大型組件。這也是我寫做這篇文章的緣由。
https://blog.csdn.net/GitChat/article/details/78086852