#每日一記#前端與后端交互 數(shù)據(jù)狀態(tài)設(shè)計(jì) 最佳實(shí)踐

每日一記 - 但并不日更

在前端頁(yè)面開發(fā)中镶摘,大部分的時(shí)間都是在與后端進(jìn)行數(shù)據(jù)交互:獲取數(shù)據(jù)嗽桩、計(jì)算并渲染。而頁(yè)面上又有大量的元素狀態(tài)需要維護(hù)凄敢,顯示碌冶、隱藏、變化涝缝。這些都可能讓我們焦頭爛額扑庞,然后在一周后看不懂自己的代碼譬重。

無奈

所以項(xiàng)目開發(fā)的過程中需要一個(gè)規(guī)范來約束代碼的走向,讓代碼能按照統(tǒng)一的罐氨、最高效的方式運(yùn)行(還有讓別人閱讀)害幅。這里介紹一個(gè)前端對(duì)接后端接口數(shù)據(jù)的一個(gè)最佳實(shí)踐。

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

  • 點(diǎn)了一個(gè)按鈕沒有反應(yīng)(以现??约啊?這按鈕壞了)邑遏,但是突然頁(yè)面像爆炸了一樣不停的刷新(-,-啊救命)
  • 進(jìn)入一個(gè)頁(yè)面恰矩,是個(gè)純白的(记盒??外傅?網(wǎng)卡了纪吮?程序報(bào)錯(cuò)了?)萎胰,返回再進(jìn)還是純白碾盟,讓你搞不清楚到底發(fā)生了什么。

這里的例子說明:如果前端開發(fā)中不能把異常描述清楚技竟、涵蓋全面冰肴,數(shù)據(jù)狀態(tài)的糟糕反饋就會(huì)直接影響用戶體驗(yàn)。

問題分析

我們先從最簡(jiǎn)單的情況入手榔组,一個(gè)頁(yè)面使用一個(gè)接口熙尉。這種情況下通常是:

  • 全量獲取列表
  • 獲取主頁(yè)詳情
  • 發(fā)布一張圖片
  • 搜索關(guān)鍵詞
  • ···

這樣的情況又分兩種,

  • 進(jìn)入頁(yè)面時(shí)獲取數(shù)據(jù) -> 渲染頁(yè)面
  • 進(jìn)入頁(yè)面后進(jìn)行操作 -> 得到反饋 -> 渲染頁(yè)面搓扯。

不論是哪一種情況检痰,我們都只在一個(gè)頁(yè)面里處理一個(gè)接口,這是最簡(jiǎn)單的情況锨推。那么我們來看一下下面的圖片铅歼,并把它當(dāng)作一個(gè)開發(fā)任務(wù)思考一下你會(huì)怎么處理。

老板來了個(gè)需求

如果你只想到了「調(diào)用接口」「渲染頁(yè)面」里爱态,那你這篇文章就是為你寫的(笑)谭贪。其實(shí)上面的圖只向你展示了兩個(gè)狀態(tài)「初始狀態(tài)」「理想結(jié)果狀態(tài)」锦担,我用了「理想結(jié)果」這個(gè)詞來描述這個(gè)狀態(tài)俭识,是因?yàn)檫@是我們?cè)谝磺胁僮鞫纪昝赖那闆r下得到的理想狀態(tài)。

而通常在項(xiàng)目里你只會(huì)從別人手里得到這兩張圖洞渔,我說的對(duì)嗎套媚?(產(chǎn)品經(jīng)理和設(shè)計(jì)師都默認(rèn)你了解他們需要的一切)缚态。

如果我們希望做一個(gè)優(yōu)秀的前端,我們就需要立刻發(fā)現(xiàn)這里還缺少了三張圖(三個(gè)狀態(tài))(有些交互里并不需要這么多狀態(tài)堤瘤,這里只討論最全面的情況)

數(shù)據(jù)獲取中狀態(tài)
無數(shù)據(jù)狀態(tài)
數(shù)據(jù)異常狀態(tài)

一個(gè)接口的5個(gè)狀態(tài)

需求分析

從調(diào)用一個(gè)接口到渲染頁(yè)面我們大致分為一下幾部

調(diào)用接口 -> 得到數(shù)據(jù) -> 處理數(shù)據(jù) -> 渲染

初始狀態(tài)

接下來我們來編寫一些代碼玫芦,來對(duì)接接口并且管理數(shù)據(jù)和狀態(tài)。為了使代碼更加聚合本辐,用一個(gè)字面量對(duì)象 SeaerchInput 來維護(hù)狀態(tài)桥帆。然后我們模擬一個(gè)接口的調(diào)用。

// 以上面搜索為例

// 創(chuàng)建頁(yè)面對(duì)象
let SearchInput = {}

// 模擬一個(gè)接口
function API () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      let result = [{
        name: '李三'
      }];
      resolve(result);
    })
  })
}

理想結(jié)果狀態(tài)

接下來我們?cè)?code>SearchInput中用data字段保存數(shù)據(jù)慎皱,用getSearchResult()方法綁定數(shù)據(jù)老虫,調(diào)用接口并直接綁定數(shù)據(jù),那么我們將得到的「理想結(jié)果狀態(tài)」茫多。

let SearchInput = {
  data: null,

  getSearchResult() {
    API.then(
      (res) => {
        this.data = res; // 綁定數(shù)據(jù)
      }
    )    
  }
}

SearchInput.getSearchResult();  // 獲取數(shù)據(jù)

function API () {
  return new Promise(function (resolve, reject) {
    // ...
  })
}
// html 的語法將使用 angular 指令去表達(dá)
<div>
  <!-- 渲染結(jié)果 -->
  <p ng-repeat="result in SearchInput.data"></p>
</div>

這樣的代碼是十分脆弱的祈匙,因?yàn)槲覀円呀?jīng)默認(rèn)數(shù)據(jù)會(huì)瞬間返回并且沒有任何問題。

數(shù)據(jù)獲取中狀態(tài)

function API () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      let result = [{
        name: '李三'
      }];
      resolve(result);
    }, 3000)  // 為接口增加3秒的延時(shí)
  })
}

一旦給API增加點(diǎn)延時(shí)天揖,就會(huì)發(fā)現(xiàn)頁(yè)面會(huì)在純白狀態(tài)下停留很久夺欲,因?yàn)轫?yè)面沒有任何提示,所以用戶根本無法知道發(fā)生了什么事情今膊,是等待還是返回些阅?

為此我們需要管理從接口發(fā)起請(qǐng)求(request)到接收響應(yīng)(response)這段時(shí)間的狀態(tài),在SearchInput中用hasDone來保存接口的響應(yīng)狀態(tài)万细,null代表這個(gè)接口還在初始化狀態(tài)扑眉,false代表已經(jīng)發(fā)出請(qǐng)求但未收到響應(yīng),true代表已經(jīng)收到響應(yīng)赖钞。

let SearchInput = {
  data: null,
  hasDone: null, // 初始化

  getSearchResult() {
    this.hasDone = false; // 發(fā)起請(qǐng)求時(shí)置為 false

    API.then(
      (res) => {
        this.hasDone = true; // 收到響應(yīng)時(shí)置為 true
        this.data = res;
      }
    )    
  }
}

SearchInput.getSearchResult();

function API () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      // ...
    }, 3000)  // 為接口增加3秒的延時(shí)
  })
}
<!-- 數(shù)據(jù)獲取中狀態(tài) -->
<div ng-if="SearchInput.hasDone === false">
  loading
</div>

<div ng-if="SearchInput.hasDone">
  <!-- 渲染結(jié)果 -->
  <p ng-repeat="result in SearchInput.data"></p>
</div>

這下好了,如果接口很慢頁(yè)面也會(huì)顯示 loading聘裁,用戶不會(huì)為此不知所措了雪营。

數(shù)據(jù)異常狀態(tài)

盡管現(xiàn)在網(wǎng)絡(luò)和服務(wù)器已經(jīng)十分穩(wěn)定,很少會(huì)出現(xiàn)異常衡便,但是無論是網(wǎng)絡(luò)献起、服務(wù)器或代碼哪一個(gè)出現(xiàn)異常而沒有考慮,那都會(huì)造成用不好的用戶體驗(yàn)

function API () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      let error = '服務(wù)器異常';
      reject(error);  // 接口返回了異常
    })
  })
}

現(xiàn)在我們假設(shè)我們的API返回了異常镣陕,頁(yè)面又會(huì)變?yōu)榧儼琢饲床停瑳]有任何數(shù)據(jù)顯示也沒有任何提示。

為此我們需要一個(gè)狀態(tài)來管理接口返回的狀態(tài)呆抑,在SearchInput中用hasSuccess來保存接口的返回狀態(tài)岂嗓,null代表還在初始化狀態(tài),false代表接口返回失敗鹊碍,true代表接口成功返回?cái)?shù)據(jù)厌殉。(你甚至可以先判斷數(shù)據(jù)的格式食绿、數(shù)量等是否滿足你的要求,如果不滿足要求公罕,即使接口返回了數(shù)據(jù)器紧,你一樣可以將hasSuccess設(shè)置為false,因?yàn)檫@里的 success 代表了你得到了可以正確使用的數(shù)據(jù)楼眷,而不僅僅是得到了數(shù)據(jù))

let SearchInput = {
  data: null,
  hasDone: null, 
  hasSuccess: null, // 初始化

  getSearchResult() {
    this.hasDone = false;

    API.then(
      (res) => {
        this.hasDone = true;
        this.hasSuccess = true; // 得到數(shù)據(jù)置為 true
        this.data = res;
      },
      (err) => {
        this.hasDone = true; // 此時(shí)我們也要更新 hasDone
        this.hasSuccess = false; // 發(fā)生異常置為 false
      }
    )    
  }
}

SearchInput.getSearchResult();

function API () {
  return new Promise(function (resolve, reject) {
    // ...
  })
}
<!-- 數(shù)據(jù)獲取中狀態(tài) -->
<div ng-if="SearchInput.hasDone === false">
  loading
</div>

<!-- 數(shù)據(jù)異常狀態(tài) -->
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess === false">
  數(shù)據(jù)異常
</div>

<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess">
  <!-- 渲染結(jié)果 -->
  <p ng-repeat="result in SearchInput.data"></p>
</div>

現(xiàn)在我們會(huì)在hasDone === true后知道數(shù)據(jù)是否正常铲汪,并且給出了錯(cuò)誤的提示。

無數(shù)據(jù)狀態(tài)

最后一個(gè)狀態(tài)也是我們要考慮的罐柳,當(dāng)用戶嘗試搜索一個(gè)詞卻什么都沒返回桥状,又變成了可惡的純白界面,我們還需要考慮一下當(dāng)獲取數(shù)據(jù)時(shí)什么都沒有的情況硝清。

function API () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      let result = [];  // 現(xiàn)在沒有任何結(jié)果
      resolve(result);
    })
  })
}

我們需要一個(gè)狀態(tài)來管理數(shù)據(jù)的狀態(tài)辅斟,在SearchInput中用hasData來保存數(shù)據(jù)狀態(tài),null代表還在初始化中芦拿,false代表數(shù)據(jù)為空士飒,true代表數(shù)據(jù)不為空。

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蔗崎,沒有數(shù)據(jù)置為 false
        this.data = res;
      },
      (err) => {
        this.hasDone = true;
        this.hasSuccess = false; 
        this.hasData = false; // 失敗肯定沒有數(shù)據(jù)了
      }
    )    
  }
}

SearchInput.getSearchResult();

function API () {
  return new Promise(function (resolve, reject) {
    // ...
  })
}
<!-- 數(shù)據(jù)獲取中狀態(tài) -->
<div ng-if="SearchInput.hasDone === false">
  loading
</div>

<!-- 數(shù)據(jù)異常狀態(tài) -->
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess === false">
  數(shù)據(jù)異常
</div>

<!-- 無數(shù)據(jù)狀態(tài) -->
<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess && SearchInput.hasData === false">
  數(shù)據(jù)異常
</div>

<div ng-if="SearchInput.hasDone && SearchInput.hasSuccess && SearchInput.hasData">
  <!-- 渲染結(jié)果 -->
  <p ng-repeat="result in SearchInput.data"></p>
</div>

現(xiàn)在上面的代碼基本上就是你所需要的了酵幕,它可以幫你應(yīng)對(duì)各種情況,讓頁(yè)面展示的更加完美缓苛。

實(shí)踐分析

這一大段代碼就是對(duì)應(yīng)一個(gè)簡(jiǎn)單接口五個(gè)狀態(tài)的設(shè)計(jì)芳撒,也是我目前項(xiàng)目中使用的模式,雖然看上去比較繁瑣未桥,但是相比后期再不停的補(bǔ)充和修改笔刹,一次性考慮全面帶來很多好處。

如果一個(gè)接口是為了實(shí)現(xiàn)分頁(yè)加載冬耿,那么狀態(tài)的數(shù)量又會(huì)有所提升舌菜,這篇文章不再闡述。

如果一個(gè)頁(yè)面使用了多個(gè)接口亦镶,數(shù)據(jù)和狀態(tài)之間產(chǎn)生了交叉日月,為了使?fàn)顟B(tài)邏輯清晰應(yīng)該合理利用字面量對(duì)象來聚合代碼邏輯。

在多人協(xié)作方面,由于大家使用同一套規(guī)范,對(duì)代碼的閱讀速度有顯著提高评腺。

這里列出的代碼以普及為主希痴,很多實(shí)現(xiàn)細(xì)節(jié)方面都可以再去優(yōu)化,提煉。甚至寫一個(gè)構(gòu)造函數(shù)也是很方便的選擇蜕琴。

感謝閱讀

羅小黑寫寫文字

如果喜歡文章 請(qǐng)留下一個(gè)贊~
如果喜歡文章 分享給更多人~

掘金中關(guān)注我
簡(jiǎn)書中關(guān)注我

自由轉(zhuǎn)載-非商用-非衍生-保持署名(創(chuàng)意共享3.0許可證
轉(zhuǎn)載時(shí)請(qǐng)保留原文鏈接 以保證可及時(shí)獲取對(duì)文章的訂正和修改

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挣轨,一起剝皮案震驚了整個(gè)濱河市串前,隨后出現(xiàn)的幾起案子瘫里,更是在濱河造成了極大的恐慌,老刑警劉巖荡碾,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谨读,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坛吁,警方通過查閱死者的電腦和手機(jī)劳殖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拨脉,“玉大人哆姻,你說我怎么就攤上這事∶蛋颍” “怎么了矛缨?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)帖旨。 經(jīng)常有香客問我箕昭,道長(zhǎng),這世上最難降的妖魔是什么解阅? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任落竹,我火速辦了婚禮,結(jié)果婚禮上货抄,老公的妹妹穿的比我還像新娘述召。我一直安慰自己,他們只是感情好蟹地,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布积暖。 她就那樣靜靜地躺著,像睡著了一般锈津。 火紅的嫁衣襯著肌膚如雪呀酸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天琼梆,我揣著相機(jī)與錄音,去河邊找鬼窿吩。 笑死茎杂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纫雁。 我是一名探鬼主播煌往,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了刽脖?” 一聲冷哼從身側(cè)響起羞海,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎曲管,沒想到半個(gè)月后却邓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡院水,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年腊徙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檬某。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撬腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恢恼,到底是詐尸還是另有隱情民傻,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布场斑,位于F島的核電站漓踢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏和簸。R本人自食惡果不足惜彭雾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锁保。 院中可真熱鬧薯酝,春花似錦、人聲如沸爽柒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浩村。三九已至做葵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間心墅,已是汗流浹背酿矢。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怎燥,地道東北人瘫筐。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像铐姚,于是被迫代替她去往敵國(guó)和親策肝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容