前言: 在實際的開發過程當中,前、後端仍然存在過程式子的耦合,即前端人員過分的依賴服務端所提供的數據。這不只會耽誤前端開發的進度,也容易在聯調過程當中致使一些沒必要要的麻煩。所謂,先後端徹底分離,就是要提供一種機制讓前端開發人員再也不依賴服務端的數據,而是接口。
在實際的項目中,前端開發人員每每會遇到如下的問題而致使前端進度的緩慢:javascript
致使這種現象的根本緣由就是前端過分依賴服務端,爲了能解開這種高密度的耦合,咱們首先是要創建公約制接口,而後按照接口進行離線開發。這樣,咱們能夠先按照預先的接口進行模擬,待到雙方條件具有時,按照接口進行聯調測試,就方便許多。前端
要想讓前端隔離於服務端,就要在前端去作數據模擬——Mock。數據模擬的方式有不少,大體能夠分爲三類:vue
第一種方式是不值得提倡的,由於這樣作將破壞ajax原有功能,也會造成許多垃圾代碼,致使後期維護難度大,例如:java
Axios.get('/api/users') .then(/* ... */) .catch(err => { const data = { /* ...*/ }; /* 對data的處理 */ })
catch真正應該構建的不該該是數據模擬,而是對頁面上交互流程的處理,例如在數據請求失敗時,給予用戶相應的提示。webpack
對於第三種方式,也是比較容易實現的,在webpack中配置一下代理便可,而後請求時,將原有接口改成代理連接便可。如今,各類第三方MockServer已經很是成熟,但卻依賴緩落環境,不適合離線開發的場景。若是本身有搭建服務器的能力,也能夠搭建本地MockServer,但其使用成本沒有第二種來的方便。ios
最後來講說第二種方式,這也是我我的最傾向的一種方式。在vue體系中,最多見的就是使用MockJS來攔截Axios的請求。但在實際使用過程當中,語法上仍然是比較晦澀的,咱們來看看使用原生的MockJS是如何構建攔截服務的:git
import Mock from 'mockjs'; // 攔截請求-動態路由 get /api/user/:id Mock.mock(/\/api\/user\/[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}/, 'GET', options => { return { /* 返回的模擬數據 */}; }); // 攔截請求-參數化路由 get /api/users? Mock.mock(/\/api\/user+(\?{0,1}(([A-Za-z0-9-~]+\={0,1})([A-Za-z0-9-~]*)\&{0,1})*)$/, GET, options => { return { /* 返回的模擬數據 */}; });
看看這樣的代碼,是否是發現特別的晦澀難懂,並且還不太容易維護,開發人員要手寫一段更復雜的mock也會很容易寫錯。架構的本質是讓複雜的事情變得簡單,所以,在此基礎上,咱們須要多Mock進行改進。github
我一直在思考一個問題,爲何Mock的寫法不能和AJAX的寫法一一對應呢?AJAX能夠採用RESTFUL的方式來書寫,Mock也應當按照這種方式來書寫。構成這一想法的啓蒙框架就是EXPRESS,EXPRESS提供了RESTFUL方式的路由,方便開發者將項目按照路由地址進行模塊化。所以,咱們就須要有以下的指望:web
// AJAX請求 Axios.get('/api/user/10000') .then(res => { /* 處理 res.data */}); // Mock模擬 Mock.get('/api/user/:id', id => { return { /* 返回數據 data */ }; })
看看這樣的指望,AJAX發送請求後,能拿到Mock返回的數據data。於此同時,Mock模擬的數據能攔截全部諸如/api/user/:id
的請求,這裏佔位符拿到的數據爲id = 10000
,而且在Mock的回調函數中咱們還能夠作動態處理。ajax
經過編碼,咱們對Mock進行相應的擴展,其最終功能以下:
// 按照數據模板返回模擬數據 Mock.mock({ /* MockJS template */ }); // 語義化RESTFUL請求模擬 Mock.get('/api/users', () => {}); Mock.post('/api/user', () => {}); // 動態路由模擬 Mock.get('/api/user/:id', id => {}); // get請求參數攔截 Mock.get('/api/user/:id', (id, params) => {}); // post請求參數攔截 Mock.post('/api/user', data => {}); // 同、異步返回 Mock.get('/api/user/:id', id => { return { /* 返回數據 data */ }; }); Mock.get('/api/user/:id', id => { return Promise.resolve({ /* 返回數據 data */ }); }); // 延時返回數據, 4秒後傳回數據 Mock.get('/api/users', () => { return { /* 返回數據 data */ }; }, {timeout: 4000}); // 異常code返回 Mock.get('/api/users', () => { return { /* 返回數據 data */ }; }, {code: 500});
經過這樣的封裝,咱們基本已經能夠快速模擬客戶端的全部請求,並模擬大部分可能性狀況對客戶端作出響應。在實現過程當中,作了兩個特別的處理:
:id
,請求參數分爲data和params,data參數針對post請求,params針對get請求,每一種請求只能攔截一種。佔位符參數永遠優先與請求參數,且data參數和params不會共存。CRUD
。而且咱們支持多種前端數據庫,大部分能夠連接到IndexDB、WebSQL,此時RouteMock對異步返回的數據有很好的支持的。啥也不說,咱們上代碼:
// EXPRESS所用到的路徑轉正則庫 import pathTo from 'path-to-regexp'; // https://github.com/ctimmerm/axios-mock-adapter import MockAdapter from 'axios-mock-adapter'; import Mock from 'mockjs'; // 封裝的AJAX庫 import { instance } from '@/lib/ajax'; import url from 'url'; import qs from 'qs'; // 實例化數據模擬器 const AdapterMock = new MockAdapter(instance); const mock = { mock: template => Mock.mock(template) }; const httpMethods = ['get', 'post', 'patch', 'put', 'delete', 'head', 'options']; httpMethods.forEach((type) => { const mockMethod = 'on' + type.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); mock[type] = function (urlReqExpString, callback, {code = 200, timeout = 0} = {}) { // 將url匹配表達式進行轉換, 例如: /path/:id const rurl = pathTo(urlReqExpString, [], { // 不忽略大小寫 sensitive: true, strict: true }); // 對get請求進行甄別,由於get請求有`?`參數 if (type === 'get') { // 將url正則兩邊的`/`去掉,並追加`?`參數的正則校驗 const urlString = rurl .toString() .slice(1, -1) .replace('$', '\\/{0,1}(\\?{0}|\\?{1}((\\S+\\={1})(\\S+)\\&{0,1})*)$'); // 從新編譯正則規則 rurl.compile(urlString); } AdapterMock[mockMethod](rurl).reply(async config => { const urlSchema = url.parse(config.url); // 攔截的佔位符參數,去除`?`校驗所帶的四組括號,以及第一項url const argsArr = rurl.exec(urlSchema.pathname); const args = argsArr.slice(1, type === 'get' ? argsArr.length - 4 : argsArr.length); // get 提交參數 const params = config.params || qs.parse(urlSchema.query); // post 提交參數 let datas; try { // 對報文進行JSON轉換,若是轉換失敗,說明不是JSON格式,直接返回 datas = JSON.parse(config.data); } catch (e) { datas = config.data; } args.push(type === 'get' ? params : datas); const result = Mock.mock(await callback.call(config, ...args)); // return result === undefined ? {} : result; return new Promise((resolve, reject) => { setTimeout(() => { resolve([code, result]); }, timeout); }); }); }; }); export default mock;
雖然模塊的構建已經完成,然而在推廣的時候遭遇到巨大的阻力,這也反映了Mock自己所帶來的問題——功能雞肋。由於開發過程當中,某些接口並非一開始就定義好的,即使定義好了也會有修改的可能。一旦有所變化,其代碼修改的工做量也是挺大的,模擬數據的地方和接入數據的地方都要改動。
咱們將開發過程分爲五個模式:
對於本地開發模式,其實用性仍然比較強的,而且對於本地聯調和生產演示都是可複用的,改動量不大的。而接口的改動的確在所不免,這在開發過程當中須要明確以及協調。前端是一個工做量相對於服務端要大的多的工做環境,除了業務邏輯、界面樣式、交互流程,還要等待服務端數據進行實時對接,下降這總服務間的耦合,可讓前端將關注點真正放在前端工程的構建上,而不是數據。