loading 动画 放在请求拦截里做统一处理,还是放在每个页面里

关于loading 动画,目前有两种方案:
1、放在请求拦截里,每次发起请求,都展示loading 动画。
优:这样做的好处是不用每次都写loading 的开与关

2、放在页面里,对每次请求处理(单个或多个请求)做loading 的开与关。
缺:每个页面或请求里都得写loading 的开关逻辑,很繁琐。

以上为个人观点,欢迎大家讨论与补充。

阅读 1.3k
评论
    10 个回答

    其实就是逻辑统一处理,还是每个页面单独处理的区别。

    简单聊聊我所知道的几种不同方案

    image.png

    一、在请求接口时拦截,例如axios的拦截器

    这个方案就是题主说的第一种方案。这种方案如果结合vue或者react中使用,那么我们必须要思考的一个问题是,这个loading是代表的那种loading。

    很显然,一定得是全局loading,否则很多细节无法处理,例如如果有多个接口同时请求怎么办?因此,要把这种方案实施下来,就需要

    1. Loading组件独立于所有页面组件,是公用的全局组件
    2. 多个请求时,需要计算正在请求的个数

    如果从代码量考虑,这样的方式确实减少了工作量,但是缺陷也很明显,缺乏灵活性。只要有接口请求,就只能把这个全局Loading抬出来。很多场景无法适应。

    当然,如果是后台管理系统,不需要Loading具有太多的灵活性,是可以这样处理的。就看你能不能说服团队其他所有人,这个项目,所有的加载情况都这样处理。

    二、在组件中拦截,例如ant design pro中使用的dva-loading

    dva-loading是react-redux解决方案中的中间件。dva-loading在全局store中维护了一个EffectLoading的对象,所有loading的状态请求都放在了该对象中,并且每个请求根据namespace的不同都有对应的唯一值。也就是说,这种方式避免了整个全局中有一个loading,而是把所有的loading状态各自有一个唯一的标识能够访问得到。

    // EffectLoading
    {
      effects: {
        'login/fetch': true,
        'login/user': true,
        'dashboard/fetch': false,
        'dashboard/list': false,
      },
      global: false,
      models: {
        'login/fetch': true,
        'login/user': true,
      }
    }

    这种方式也是统一处理,但是处理的方式和第一种方式不一样,这种方式更具灵活性,我们可以在组件里针对自己当前组件的状态进行不同的处理。该方式仅仅只是把对应请求的loading传入组件,在组件中如何处理,则自己决定。

    这种方式由于第一种方式,当然两种方式的处理思维也可以结合起来,糅合成更合理的方案

    但是这样的方案在react中也并非没有缺陷。

    dva-loading让整个项目全局共享一个loadingEffect对象。

    也就是说,无论你的组件如何划分,loading都不可避免的成为了共享状态。可是在很多情况下,我们仅仅只希望loading成为当前页面或者某个子组件的私有状态。因此dva-loading虽然看上去简化了写法,却让组件化思维固化,失去了灵活性。

    也正因为处于最顶层的共享数据,当loadingEffect状态改变时,会导致额外的冗余渲染。因此,dva-loading应该慎用。

    三、每个组件,每个请求维护自己的loading状态

    当然,这样的方式灵活性都很好,无论交互想要怎么处理,都可以完美解决。缺点也很明显,完美要额外定义很多loading状态,维护成本会稍高,代码也会多些一点。

    四、在react中,利用自定义hooks

    这是目前我认知到的最完美的方案。

    超性感的的react hooks(五):自定义hooks中我跟大家介绍了如何使用自定义hooks。
    并在使用hooks重构antd pro的想象力(三)我是如何利用hooks干掉redux的中介绍了如何自定义一个能够处理请求的hooks。

    首先我们定义一个全局的能够处理请求的自定义hooks useInitial

    【详细代码见上述文章】

    在组件中使用时,只需要如下一句代码即可拿到对应请求的loading状态和请求数据

    const {loading, data} = useInitial(api);

    该方案是将请求逻辑统一封装成为自定义hooks,极大降低了维护成本,并且每个loading都相互独立,互不影响,灵活性也不存在任何问题。

    因此我认为这是最完美的解决方案。不过要基于react hooks。

    五、选择最适合自己的方案

    结合自己团队和项目的情况,选择最适合自己的方案,才是最好的方案!

    评论 赞赏

      并不建议放在拦截器请求里处理loading
      1.业务需求有些请求不需要告知客户
      2.页面请求数量不固定
      3.高类聚低耦合,拦截器里面处理组件业务不怎么合适

      评论 赞赏

        对于loading展示具体要看你的业务是否需要给用户提示,例如:提交,删除,添加,获取列表数据,获取详情数据等等,这些是可以给loading的。不需要loading的(例如:一个页面有多个接口调用,只需要给主要展示数据的loading,其余的就不用给了)不建议放在请求拦截里,你这样做了就等于每请求一个接口都会有loading这肯定是不好的。loading的开关其实就是一个方法的事,例如:mint-ui

        Indicator.open(); //打开
        Indicator.close(); //关闭
        评论 赞赏
          PeanutQAQ
          • 1
          • 新人请关照

          下面给出 AsyncButton 伪代码实现,其余 loading 组件可类似书写。

          class AsyncButton extends React.Componment {

          handleClick = async (e, btn) => {
              btn.loading(true);
              try {
                  await this.props.onClick(e);
              }   finally {
                  btn.loading(false);
              }
          }
          
          render() {
             const propsTemp = _.cloneDeep(this.props);
             propsTemp.onClick && delete propsTemp.onClick;
          
              return <Button {...propsTemp} onClick={handleClick} />  
          }

          }

          评论 赞赏
            leehu
            • 140

            首先我们需要搞清楚loading的用处,第一:告诉用户你正在发起一个请求,第二:告诉用户服务请求还没有回来处于等待状态。两则的呈现形式在每个端都有不同的形式:

            第一种,我们可以借鉴其他平台的处理方式,比如iOS在所有的请求发起的时候会开启Indicator(单例),等待结束之后然后关闭,他的load是显示在顶部的statusbar,相对于内容来说并没有阻碍到用户体验。

            [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

            如果你也是这样的需求,可以将loading放到请求拦截里,然后loading放置的位置应该不能影响用户的交互。比如下面的右上角

            第二种,你可以看到这几年无论是web还是iOS、android的应用中,为了提升应用的加载等待这段时间的用户感知体验,各种奇门遁甲之术层出不穷,像客户端那种hub的形式对用户来说极其不友好已经被很多应用抛弃使用了,其中Skeleton就在这几年流传开来。像此类的需求,处理方式建议可以放到页面层或api层。

            1. 组件方式
            <page :empty="!data" :indicator="show" \>
            ....
            </page\>

            loading组件或者emptyDataSet组件会在page组件中处理的挂载和卸载,尤其是在pc端,你可以遇到每个section就有一个request的情况,你都需要告诉用户词section正在加载
            类似的在客户端中的处理方式

            - (LFTableCellItem *)cellItemAtIndexPath:(NSIndexPath *)indexPath {
             LFTableCellItem *theCellItem = nil;
             if ([[self.lfDelegate  cellItemsForTableView:self] count] == 0) {
                 if (_firstLoad) {
                     self.scrollEnabled = NO;
                     return self.loadingCellItem;
                 } else {
                    self.scrollEnabled = self.allowScrolling;
                    return self.emptyCellItem;  
                 }
             } else {
                .....
             }
             return theCellItem;
            }
            1. 在api层处理的方式
            axios.get('/user/12345', {
              cancelToken: source.token,
              showLoading: true
            })

            然后通过请求拦截层,做相应的处理,这样做的话在分页加载或者下拉刷新的时候showLoading都需要得到响应的控制,成本比较高。
            客户端类似的情况可以使用URLProtocol方式来拦截请求,通过标志位来判断是否显示

            评论 赞赏
              大咪咪
              • 3
              • 新人请关照

              我认为应该写在拦截器里面。

              代码优雅,维护简单,不用每个页面都写。

              至于楼下提到的有些请求不需要告知用户,可以在拦截器中配置参数来控制是否显示loading

              代码耦合就看自己选择了,每个页面写其实也很繁琐,我个人建议在拦截器中处理

              评论 赞赏

                这个问题还可以延伸到请求的错误处理,请求拦截器里做统一处理,还是页面里,单独处理。欢迎讨论

                评论 赞赏
                  Allen
                  • 1
                  • 新人请关照

                  关于loading 需要按照场景区分,此处主要是用户体验的提升,关键点在于,当用户有操作,则需要及时的反馈。

                  1、首次加载页面还未能获取数据,此时loading,骨架屏都较为合适。

                  2、如果是列表页面加载,分页切换,此时适合每次请求都有一个loading「在更新数据的地方,而不是全屏的loding」

                  3、业务相关请求,统计信息,则不需要给用户显示。

                  4、关于放哪合适,不同场景,不同的操作方式,关键是看code是否优雅。

                  评论 赞赏

                    我的做法是列表查询才在表格里loading,这个通常表格属性里已经自带loading字段了,只需要给这个字段赋值true或false即可。至于其他接口,就算了,因为其他接口基本都是比列表查询快的,那么快,你还需要loading?我认为不需要了。

                    评论 赞赏
                      henry
                      • 13

                      request.js:

                      /**
                       * get
                       * @param  {String} url -必选   [地址]
                       * @param  {Object} params -可选 [参数]
                       * @param  { Boolean } isLoading -可选 [是否显示加载状态, 默认渐变显示, 初始为透明, 2s 后显示, 通过 css 控制]
                       * @return {Object}        [Promise]
                       */
                      export const $get = (url, params, isLoading = true) => {}

                      main.css:

                      .el-loading-mask.is-fullscreen {
                        background: rgba(255, 255, 255, .9);
                        transition: opacity .3s 2s;
                      }
                      评论 赞赏
                        撰写回答

                        登录后参与交流、获取后续更新提醒