#每日一记#前端与后端交互 数据状态设计 最佳实践

每日一记 - 但并不日更

在前端页面开发中,大部分的时间都是在与后端进行数据交互:获取数据、计算并渲染。而页面上又有大量的元素状态需要维护,显示、隐藏、变化。这些都可能让我们焦头烂额,然后在一周后看不懂自己的代码。

无奈

所以项目开发的过程中需要一个规范来约束代码的走向,让代码能按照统一的、最高效的方式运行(还有让别人阅读)。这里介绍一个前端对接后端接口数据的一个最佳实践。

先让我们看看反例,不知道你是不是用过这样的 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许可证
转载时请保留原文链接 以保证可及时获取对文章的订正和修改

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容