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

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

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

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

阅读 11.4k
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(); //关闭
新手上路,请多包涵

下面给出 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} />  
}

}

首先我们需要搞清楚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方式来拦截请求,通过标志位来判断是否显示

新手上路,请多包涵

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

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

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

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

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

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

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

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

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

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

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

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;
}
推荐问题
宣传栏