美文网首页前端开发那些事Web前端之路每日一记
#每日一记#前端与后端交互 数据状态设计 最佳实践

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

作者: 罗小黑写写文字 | 来源:发表于2018-04-03 20:44 被阅读63次
    每日一记 - 但并不日更

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

    无奈

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

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/ijvzyxtx.html