在前端頁面開發中,大部分的時間都是在與後端進行數據交互:獲取數據、計算並渲染。而頁面上又有大量的元素狀態須要維護,顯示、隱藏、變化。這些均可能讓咱們焦頭爛額,而後在一週後看不懂本身的代碼。javascript
因此項目開發的過程當中須要一個規範來約束代碼的走向,讓代碼能按照統一的、最高效的方式運行(還有讓別人閱讀)。這裏介紹一個前端對接後端接口數據的一個最佳實踐。html
先讓咱們看看反例,不知道你是否是用過這樣的 app:前端
這裏的例子說明:若是前端開發中不能把異常描述清楚、涵蓋全面,數據狀態的糟糕反饋就會直接影響用戶體驗。java
咱們先從最簡單的狀況入手,一個頁面使用一個接口。這種狀況下一般是:後端
這樣的狀況又分兩種,服務器
不管是哪種狀況,咱們都只在一個頁面裏處理一個接口,這是最簡單的狀況。那麼咱們來看一下下面的圖片,並把它看成一個開發任務思考一下你會怎麼處理。網絡
若是你只想到了**「調用接口」「渲染頁面」裏,那你這篇文章就是爲你寫的(笑)。其實上面的圖只向你展現了兩個狀態**:「初始狀態」、「理想結果狀態」,我用了「理想結果」這個詞來描述這個狀態,是由於這是咱們在一切操做都完美的狀況下獲得的理想狀態。app
而一般在項目裏你只會從別人手裏獲得這兩張圖,我說的對嗎?(產品經理和設計師都默認你瞭解他們須要的一切)。函數
若是咱們但願作一個優秀的前端,咱們就須要馬上發現這裏還缺乏了三張圖(三個狀態)(有些交互裏並不須要這麼多狀態,這裏只討論最全面的狀況)優化
數據獲取中狀態 無數據狀態 數據異常狀態
從調用一個接口到渲染頁面咱們大體分爲一下幾部
調用接口 -> 獲得數據 -> 處理數據 -> 渲染
接下來咱們來編寫一些代碼,來對接接口而且管理數據和狀態。爲了使代碼更加聚合,用一個字面量對象 SeaerchInput
來維護狀態。而後咱們模擬一個接口的調用。
// 以上面搜索爲例
// 建立頁面對象
let SearchInput = {}
// 模擬一個接口
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let result = [{
name: '李三'
}];
resolve(result);
})
})
}
複製代碼
接下來咱們在SearchInput
中用data
字段保存數據,用getSearchResult()
方法綁定數據,調用接口並直接綁定數據,那麼咱們將獲得的「理想結果狀態」。
let SearchInput = {
data: null,
getSearchResult() {
API.then(
(res) => {
this.data = res; // 綁定數據
}
)
}
}
SearchInput.getSearchResult(); // 獲取數據
function API () {
return new Promise(function (resolve, reject) {
// ...
})
}
複製代碼
// html 的語法將使用 angular 指令去表達
<div>
<!-- 渲染結果 -->
<p ng-repeat="result in SearchInput.data"></p>
</div>
複製代碼
這樣的代碼是十分脆弱的,由於咱們已經默認數據會瞬間返回而且沒有任何問題。
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let result = [{
name: '李三'
}];
resolve(result);
}, 3000) // 爲接口增長3秒的延時
})
}
複製代碼
一旦給API
增長點延時,就會發現頁面會在純白狀態下停留好久,由於頁面沒有任何提示,因此用戶根本沒法知道發生了什麼事情,是等待仍是返回?
爲此咱們須要管理從接口發起請求(request)到接收響應(response)這段時間的狀態,在SearchInput
中用hasDone
來保存接口的響應狀態,null
表明這個接口還在初始化狀態,false
表明已經發出請求但未收到響應,true
表明已經收到響應。
let SearchInput = {
data: null,
hasDone: null, // 初始化
getSearchResult() {
this.hasDone = false; // 發起請求時置爲 false
API.then(
(res) => {
this.hasDone = true; // 收到響應時置爲 true
this.data = res;
}
)
}
}
SearchInput.getSearchResult();
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// ...
}, 3000) // 爲接口增長3秒的延時
})
}
複製代碼
<!-- 數據獲取中狀態 -->
<div ng-if="SearchInput.hasDone === false">
loading
</div>
<div ng-if="SearchInput.hasDone">
<!-- 渲染結果 -->
<p ng-repeat="result in SearchInput.data"></p>
</div>
複製代碼
這下好了,若是接口很慢頁面也會顯示 loading,用戶不會爲此不知所措了。
儘管如今網絡和服務器已經十分穩定,不多會出現異常,可是不管是網絡、服務器或代碼哪個出現異常而沒有考慮,那都會形成用很差的用戶體驗
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let error = '服務器異常';
reject(error); // 接口返回了異常
})
})
}
複製代碼
如今咱們假設咱們的API
返回了異常,頁面又會變爲純白了,沒有任何數據顯示也沒有任何提示。
爲此咱們須要一個狀態來管理接口返回的狀態,在SearchInput
中用hasSuccess
來保存接口的返回狀態,null
表明還在初始化狀態,false
表明接口返回失敗,true
表明接口成功返回數據。(你甚至能夠先判斷數據的格式、數量等是否知足你的要求,若是不知足要求,即便接口返回了數據,你同樣能夠將hasSuccess
設置爲false
,由於這裏的 success 表明了你獲得了能夠正確使用的數據,而不只僅是獲得了數據)
let SearchInput = {
data: null,
hasDone: null,
hasSuccess: null, // 初始化
getSearchResult() {
this.hasDone = false;
API.then(
(res) => {
this.hasDone = true;
this.hasSuccess = true; // 獲得數據置爲 true
this.data = res;
},
(err) => {
this.hasDone = true; // 此時咱們也要更新 hasDone
this.hasSuccess = false; // 發生異常置爲 false
}
)
}
}
SearchInput.getSearchResult();
function API () {
return new Promise(function (resolve, reject) {
// ...
})
}
複製代碼
<!-- 數據獲取中狀態 -->
<div ng-if="SearchInput.hasDone === false">
loading
</div>
<!-- 數據異常狀態 -->
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess === false">
數據異常
</div>
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess">
<!-- 渲染結果 -->
<p ng-repeat="result in SearchInput.data"></p>
</div>
複製代碼
如今咱們會在hasDone === true
後知道數據是否正常,而且給出了錯誤的提示。
最後一個狀態也是咱們要考慮的,當用戶嘗試搜索一個詞卻什麼都沒返回,又變成了可惡的純白界面,咱們還須要考慮一下當獲取數據時什麼都沒有的狀況。
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let result = []; // 如今沒有任何結果
resolve(result);
})
})
}
複製代碼
咱們須要一個狀態來管理數據的狀態,在SearchInput
中用hasData
來保存數據狀態,null
表明還在初始化中,false
表明數據爲空,true
表明數據不爲空。
let SearchInput = {
data: null,
hasDone: null,
hasSuccess: null,
hasData: null, // 初始化
getSearchResult() {
this.hasDone = false;
API.then(
(res) => {
this.hasDone = true;
this.hasSuccess = true;
this.hasData = res.length > 0; // 有置爲 true,沒有數據置爲 false
this.data = res;
},
(err) => {
this.hasDone = true;
this.hasSuccess = false;
this.hasData = false; // 失敗確定沒有數據了
}
)
}
}
SearchInput.getSearchResult();
function API () {
return new Promise(function (resolve, reject) {
// ...
})
}
複製代碼
<!-- 數據獲取中狀態 -->
<div ng-if="SearchInput.hasDone === false">
loading
</div>
<!-- 數據異常狀態 -->
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess === false">
數據異常
</div>
<!-- 無數據狀態 -->
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess && SearchInput.hasData === false">
數據異常
</div>
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess && SearchInput.hasData">
<!-- 渲染結果 -->
<p ng-repeat="result in SearchInput.data"></p>
</div>
複製代碼
如今上面的代碼基本上就是你所須要的了,它能夠幫你應對各類狀況,讓頁面展現的更加完美。
這一大段代碼就是對應一個簡單接口五個狀態的設計,也是我目前項目中使用的模式,雖然看上去比較繁瑣,可是相比後期再不停的補充和修改,一次性考慮全面帶來不少好處。
若是一個接口是爲了實現分頁加載,那麼狀態的數量又會有所提高,這篇文章再也不闡述。
若是一個頁面使用了多個接口,數據和狀態之間產生了交叉,爲了使狀態邏輯清晰應該合理利用字面量對象來聚合代碼邏輯。
在多人協做方面,因爲你們使用同一套規範,對代碼的閱讀速度有顯著提升。
這裏列出的代碼以普及爲主,不少實現細節方面均可以再去優化,提煉。甚至寫一個構造函數也是很方便的選擇。
若是喜歡文章 請留下一個贊~ 若是喜歡文章 分享給更多人~
自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證) 轉載時請保留原文連接 以保證可及時獲取對文章的訂正和修改