用“MEAN”技術棧開發(fā)Web應用
前言
不知何時突然冒出“MEAN技術棧”這個新詞,聽起來很牛逼的樣子,其實就是我們已經(jīng)熟悉了的近兩年在前端比較流行的技術,Mongodb、Express、AngularJs、Nodejs,由于這幾項技術涵蓋了從前端到后端再到數(shù)據(jù)庫,可以用他們完整的開發(fā)一個Web應用了,所以成了一個非常牛逼的組合,頗有當年LAMP的氣勢。前端要從切圖仔邁向全棧的路上,這幾門技術必須得有所涉獵。本系列文章利用自己虛構(gòu)的一個小項目為例,對“使用MEAN技術棧開發(fā)Web應用”做一個入門級的介紹。
AngularJs的爭議
Angular,簡稱ng,是Google出品的優(yōu)秀框架,在2013~2014年大紅大紫,但是國內(nèi)好像慢一拍,我從2015年才看到使用ng的項目大量出現(xiàn)。ng自出現(xiàn)伊始就有人詬病太難上手了,完全不同的開發(fā)方式,團隊開發(fā)更是不知道如何組織代碼。不過隨著jQuery這位老大哥逐漸被拋棄,大家開始慢慢接受mvvm這樣的編程思維。然而一個不好的消息是,ng團隊打算重構(gòu)的Angular2.0版本要發(fā)生重大變革,與1.0不能同日而語,雖然官方有1.0向2.0遷移的方案,但額外的工作總是不太好的,而且使用2.0還要付出更多的學習成本。
再加上今年又有React這個實力派雄起,ng的風頭頓時被搶過去了,人們又開始研究React下的編程方式。不過我估計react的真正實用也得等到一兩年后。眼下Angular1.x也仍然是一個不錯的選擇。盡管有2.0的變革,但是1.4還是一個穩(wěn)定版本,我們使用穩(wěn)定版本肯定是不會有問題的。
所以我的結(jié)論是,但用無妨,不會存在白學了這種事情,就算將來angular1.x廢棄了,你學到的編程思維還是在的。
本文討論如何使用AngularJs進行前端的架構(gòu),對于ng的基礎知識不做講解,需要了解的同學可以看我之前寫過的一個系列(點擊查看)。
練手項目簡介
為了系統(tǒng)的學習“MEAN”技術棧,我虛構(gòu)了一個小項目,先做一個介紹。
QuestionMaker,是一個用于生成調(diào)查問卷的系統(tǒng),用戶可以編輯試題(選擇題、填空題),并可以實時預覽編輯結(jié)果。然后還可以編輯一份試卷,為試卷添加試題,然后保存為一分完整的調(diào)查問卷。有點類似于調(diào)查派。先上一張截圖吧:
項目的功能主要是CRUD操作,所以非常適合angular的應用場景,雙向綁定對于實現(xiàn)實時預覽這樣的功能簡直是信手拈來。
項目的前后端是完全分離的,后端不渲染頁面,只提供數(shù)據(jù)接口,前端使用ng的動態(tài)模板來渲染頁面,通過ajax請求來獲取所需數(shù)據(jù)。
項目我已經(jīng)開源到github,有興趣的同學可以查看:https://github.com/Double-Lv/QuestionMaker
前端目錄結(jié)構(gòu)
用ng來構(gòu)建一個項目應該如何安排目錄結(jié)構(gòu)呢?為了不人工增加復雜度,我這里沒有用bower來管理依賴庫,也沒有其他文章中介紹的那樣用yeoman來生成項目,只是單純的手動來創(chuàng)建目錄,這樣可以把我們的注意力集中到項目的核心上,目錄結(jié)構(gòu)是這樣的:
前端的代碼都在src目錄下,包括入口文件index.html,這樣方便我們后續(xù)做合并壓縮等編譯工作,編譯后的文件可以一并放入dist目錄下。
首頁index.html
這是項目的入口頁面,其實就是一個大容器,在這里加載所有的js和css文件,然后提供一個視圖容器就夠了,因為從這個頁面以后,我們頁面就不再會有跳轉(zhuǎn),全部是通過前端路由來做局部刷新,首頁的代碼非常精簡:
- Question Maker
- Question Maker
入口文件app.js
有了入口頁面,還得有一個js的啟動入口,就是這個app.js了,在這里它只做了兩件事情:
1. 啟動angular,代碼只有一行:
- var app = angular.module('QMaker', ['ui.router']);
我們擁有了一個名為app的全局模塊。這里把ui.router給注入了,因為我們整個應用都用ui-router來做路由,后面會做詳細介紹。
2. 把ui-router的$state和$stateParams服務掛到$rootScope上,這樣我們在后面所有的模塊中,都能夠訪問到路由參數(shù),不必在每個地方都注入一次了。代碼也是相當簡單:
- app.run(function($rootScope, $state, $stateParams) {
- $rootScope.$state = $state;
- $rootScope.$stateParams = $stateParams;
- });
控制器合集controllers.js
controller.js里面是所有的controller定義,由于這個項目比較小,而且反正***都要合并,所以就都放在一個文件里了,這樣可以使用鏈式寫法app.controller('a', ...).controller('b', ...), 一口氣將所有的controller都定義好。如果項目比較大,controller多,可以把controllers建為一個文件夾,然后在里面放各個controller。
controller里面就是跟業(yè)務相關的一些代碼了,如試題數(shù)據(jù)的初始化,添加答案、刪除選項等操作。
但是當我們需要發(fā)起ajax請求的時候,如保存試題,就不宜在controller里面直接寫了,這樣會造成邏輯混雜代碼混亂。所有需要請求服務端的操作,我們可以抽象為一個個服務,進行“分層”,通過ng提供的service機制來做調(diào)用。
服務合集services.js
接上面,所有和試題相關的服務端請求,我們可以封裝成一個QuestionService,這個服務提供:提交試題、刪除試題、更新試題等服務,這樣層次就很清晰了。
所以,在services.js中,我們定義所有和服務相關的東西,在本項目中,我們的服務全都是ajax請求,可以用ng提供的$http服務來很方便的完成。事實上service中并不是必須寫ajax請求,凡是可以抽象理解為“公共服務”的東西,都可以定義在這里,可以被其他模塊隨意調(diào)用。
指令合集directives.js
了解過ng的同學應該對指令不會陌生,通過指令我們可以用擴展html標簽的方式來很容易的實現(xiàn)一些UI效果,使用方便、可被多個地方公共使用,就像過去我們寫jquery插件一樣。所有的指令都定義在這個文件中,同樣可以使用鏈式寫法,很爽。
在我們的項目中,有一些功能是通用的,例如列表的分頁,那么就可以把分頁功能做成一個指令。我定義了一個名為pagenav的指令,然后在所有需要用分頁的地方就可以調(diào)用了,代碼如下:
- <br>
只需一個標簽,然后通過屬性指定分頁數(shù)據(jù)和翻頁函數(shù)即可。
過濾器合集filters.js
我們的項目使用ng提供的動態(tài)模板,服務端不渲染頁面,只提供數(shù)據(jù)接口。有些數(shù)據(jù)我們需要進行格式化后進行輸出,這就用到filter了,所有的filter都放在這里。filter的定義和使用的非常簡單,此不不多述了。
前端路由定義routes.js
本項目使用ui-router來做前端路由,這個目測也是現(xiàn)在***的做法。ui-router是一個第三方插件,由于ng內(nèi)置的ngRouter功能較弱,無法實現(xiàn)嵌套路由和多視圖路由,而ui-router引入了“狀態(tài)”這個概念來控制視圖,從而實現(xiàn)這些功能,所以ui-router成了***的選擇。它是angular-ui項目(http://angular-ui.github.io/)中的一個模塊,該項目還提供了很多基于ng的ui,像日期選擇器什么的。ui-router貌似是***的一個。
用ui-router可以實現(xiàn)嵌套路由和同一頁面多視圖,具體使用方法可以參考我博客中轉(zhuǎn)載的幾篇文章(點擊查看)。
本項目中,由于整站無刷新,所以路徑的層級會比較深,嵌套路由就派上了用場。在入口頁面index.html中,用一個div來做父容器,加上ui-view屬性,就可以在里面加載別的模板了。從試題列表到試題編輯頁面的切換,就都在這個父容器中加載。
而在試題編輯頁面,又有對應的題型編輯和試題預覽視圖,通過給ui-view賦予名字,就可以加載各自對應的模板,這里就是多視圖的應用。代碼片段如下:
- <br>
在試卷預覽頁面,我們也需要對試題進行展示,只需在頁面上在定義一個ui-view,然后在路由中進行配置,就可以加載試題預覽模版,很容易的實現(xiàn)了模板的復用。
頁面中沒有任何邏輯,只需在route.js中配置好路由規(guī)則,整站無刷新跳轉(zhuǎn)就這么輕而易舉的實現(xiàn)了。
tpl目錄
利用ui-router做了前端路由后,除了入口頁面index.html外,其他所有頁面就都變成模板了(被ui-router動態(tài)加載)。所有的模板都放在tpl目錄下。如果業(yè)務的模塊較多,可以在此目錄下再新建文件夾,本項目比較簡單,所以就只有一層。不論有多少層目錄,在routers.js中配置好就OK啦。利用ui-router可以注入模板對應的控制器,所以代碼中我們也不必在加ng-controller,模板文件中就是很干凈的ng模板。
lib目錄
這里放置的是項目所需的外部庫。有angular、ui-router、jquery、bootstrap。你可以看到我只是把代碼文件給直接放里面了,沒有用當下流行的bower進行管理。是因為我不想再人為的增加復雜度,萬一有人的機器上bower安裝失敗或者git環(huán)境有問題,或者github無法訪問,都會令人十分沮喪。
反正就這幾個穩(wěn)定版本,不如直接下載過來。如果需要壓縮我后期用gulp來搞一下就行了。
總結(jié)
這個小項目的前端結(jié)構(gòu)就是這個樣子啦。從上面我們可以看出,用ng來做前端的架構(gòu)還是很有條理的。controller、service、directive這些概念,本質(zhì)上還是“模塊”,所以我們可以以模塊開發(fā)的方式來很爽快的寫代碼,文件與文件之間沒有任何耦合和依賴。模塊所需的依賴,我們通過ng的注入機制來給注入。所以在index.html中引入這些文件的時候,沒有順序要求,任意順序引入皆可。
順便說一句,前端代碼的后處理,我已經(jīng)用gulp寫好了腳本,用npm安裝所需的包后,執(zhí)行gulp就可以編譯生成dist目錄。
本文只做angular前端架構(gòu)入門級別的介紹,關于文中提到的一些具體技術細節(jié),可以查看我寫的angular系列文章(點擊查看)。
這個示例項目我已開源到github,目前已經(jīng)實現(xiàn)了基本功能。后續(xù)我會擴展更多的功能,到時候也必然會涉及到更多的技術問題,Angular進行前端架構(gòu)的路才剛剛開始。