#每日一記#前端與後端互動 資料狀態設計 最佳實踐

羅小黑寫寫文字發表於2019-02-27
每日一記 - 但並不日更

在前端頁面開發中,大部分的時間都是在與後端進行資料互動:獲取資料、計算並渲染。而頁面上又有大量的元素狀態需要維護,顯示、隱藏、變化。這些都可能讓我們焦頭爛額,然後在一週後看不懂自己的程式碼。

無奈

所以專案開發的過程中需要一個規範來約束程式碼的走向,讓程式碼能按照統一的、最高效的方式執行(還有讓別人閱讀)。這裡介紹一個前端對接後端介面資料的一個最佳實踐。

先讓我們看看反例,不知道你是不是用過這樣的 app:

  • 點了一個按鈕沒有反應(???這按鈕壞了),但是突然頁面像爆炸了一樣不停的重新整理(-,-啊救命)
  • 進入一個頁面,是個純白的(???網路卡了?程式報錯了?),返回再進還是純白,讓你搞不清楚到底發生了什麼。

這裡的例子說明:如果前端開發中不能把異常描述清楚、涵蓋全面,資料狀態的糟糕反饋就會直接影響使用者體驗。

問題分析

我們先從最簡單的情況入手,一個頁面使用一個介面。這種情況下通常是:

  • 全量獲取列表
  • 獲取主頁詳情
  • 釋出一張圖片
  • 搜尋關鍵詞
  • ···

這樣的情況又分兩種,

  • 進入頁面時獲取資料 -> 渲染頁面
  • 進入頁面後進行操作 -> 得到反饋 -> 渲染頁面。

不論是哪一種情況,我們都只在一個頁面裡處理一個介面,這是最簡單的情況。那麼我們來看一下下面的圖片,並把它當作一個開發任務思考一下你會怎麼處理。

老闆來了個需求

如果你只想到了**「呼叫介面」「渲染頁面」裡,那你這篇文章就是為你寫的(笑)。其實上面的圖只向你展示了兩個狀態**:「初始狀態」「理想結果狀態」,我用了「理想結果」這個詞來描述這個狀態,是因為這是我們在一切操作都完美的情況下得到的理想狀態。

而通常在專案裡你只會從別人手裡得到這兩張圖,我說的對嗎?(產品經理和設計師都預設你瞭解他們需要的一切)。

如果我們希望做一個優秀的前端,我們就需要立刻發現這裡還缺少了三張圖(三個狀態)(有些互動裡並不需要這麼多狀態,這裡只討論最全面的情況)

資料獲取中狀態
無資料狀態
資料異常狀態

一個介面的5個狀態

需求分析

從呼叫一個介面到渲染頁面我們大致分為一下幾部

呼叫介面 -> 得到資料 -> 處理資料 -> 渲染

初始狀態

接下來我們來編寫一些程式碼,來對接介面並且管理資料和狀態。為了使程式碼更加聚合,用一個字面量物件 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許可證
轉載時請保留原文連結 以保證可及時獲取對文章的訂正和修改

相關文章