react如何控制全局loading_React Loading组件的正确姿势

React Loading组件的正确姿势

页面异步加载的时候为缓解用户等待的焦虑情绪,我们通常会采取加载中...等文字或图标提示。

(图例,各种加载中状态)

日常写法

直接在state中存一个loading的布尔变量,请求发送前后改变状态,触发页面渲染

this.setState({

loading:true //发送之前

});

ajax("path", {a:1}, (text)=>{

this.setState({

loading:false, //正确回调函数中设置为false

text:text

});

}, ()=>{

this.setState({

loading:false, //错误回调函数中设置为false

text:"出错了"

})

});

render函数

render(){

let {loading,text} = this.state;

return

{loading ?

加载中...
: null}

{text}

;

}

上面是一个非常普通的写法,实现功能完全没问题,但不够优雅,也不具备可扩展性,试想组件中有多个请求,并且要求有好几个加载中状态,上面的代码就很难处理,要维护多个loading状态,每个请求的loading状态都至少要改变2次,就会有2次setState,而且render函数中的loading ui还得一次又一次的判断,多了太多和业务本身没有关系的代码。

如果要优化,最简单就是把Loading的ui先做成组件,组件通过props把是否显示的权限交给上层组件。例如这样

Loading ui组件化以后,我们尝试重构Ajax请求,把Loading的状态放入到组件内部去,通过观察者模式来触发Loading渲染,最终希望是下面这样。

let request = new Ajax("path", "param")

//把loading组件的引用直接传到ajax中去

request.subscribe(this.refs['loading']);

//监听请求成功

request.subscribe({

onSuccess:(text)=>{

this.setState({

text:text

})

}

});

//发起请求

request.fetch();

render函数如下

render (){

return (

{this.state.text}

)

}

ajax被作为一个发布者(观察者模式),Loading组件与当前页面的state都作为一个观察者,各自订阅ajax的不同时期的状态。

请求发送前显示Loading, 请求发送完成(不区分成功失败)隐藏Loading, 请求成功(业务上的成功)组件更新state

Ajax变成发布者

使用 ajax 继承 events.EventEmitter,让他成为一个发布者,并自定义4个事件,分别对应 发送前 成功 完成 错误 4个状态。

import events from 'events'

class Ajax extends events.EventEmitter{

constructor(url, param){

super()

this.url = url;

this.param = param;

}

subscribe(subscribe){

subscribe.onSendBefore && this.on("onSendBefore", subscribe.onSendBefore.bind(subscribe));

subscribe.onSuccess && this.on("onSuccess", subscribe.onSuccess.bind(subscribe));

subscribe.onComplete && this.on("onComplete", subscribe.onComplete.bind(subscribe));

subscribe.onError && this.on("onError", subscribe.onError.bind(subscribe));

}

fetch(){

this.emit("onSendBefore");

//使用setTimeout模拟请求延迟返回

setTimeout(()=>{

this.emit("onComplete");

this.emit("onSuccess", "hello world");

}, Math.random()*5000);

}

}

Loading组件的实现

Loading组件消费发布者2个状态,分别是发送前 完成

import React from 'react'

export default class Loading extends React.Component{

constructor(props) {

super(props);

this.state = {

display:false

}

}

onSendBefore(){ //请求发送前

this.setState({

display:true

})

}

onComplete(){ //请求发送完成

this.setState({

display:false

})

}

render(){

return this.state.display ?

加载中...
: null

}

}

经过上面的改造ajax可以被N多人订阅,分别消费他的不同状态来展现页面,逻辑清晰。

对阻断型Loading的优化

阻断型Loading 是我自己定义的概念,看文章开头的贴图,Loading其实分为2种,分别是加载中不允许用户操作(全局阻断型)和加载中允许用户操作(局部非阻断型)。另外一篇博客有详细说明#64。

全局阻断型由于其全局唯一特殊性,多个请求在他看来是连续型的,从第一个请求发起到最后一个请求完成,全局都要显示Loading。 优化如下

组件中增加了一个count计数器,请求发起计数器加1,请求结束计数器减1,计数器大于0的时候显示,否则隐藏。注:这里不把count放在state中是为了避免react的特性state更新是异步的。

import React from 'react'

export default class BlockLoading extends React.Component{

constructor(props) {

super(props);

this.count = 0;

this.state = {

display:false

}

}

onSendBefore(){

this.count++;//计数+1

this.setState({

display:this.count > 0

});

}

onComplete(){

this.count--;//计数-1

this.setState({

display:this.count > 0

});

}

render(){

return this.state.display ?

加载中...
: null

}

}

严格来说BlockLoading组件用 onShow, onHide 比使用 onSendBefore,onComplete着两个事件名命更好。后者是ajax的时间,前者才是UI组件的事件。如下是否更好

BlockLoading组件

import React from 'react'

class BlockLoading extends React.Component{

render(){

return this.props.display ?

加载中...
: null

}

}

AjaxBlockLoading组件

依然是分离逻辑,BlockLoading 是纯UI组件,AjaxBlockLoading能够相应ajax请求状态的Loading组件。

import React from 'react'

import BlockLoading from './BlockLoading'

class AjaxBlockLoading extends React.Component{

constructor(props) {

super(props);

this.count = 0;

this.state = {

display:false

}

}

onSendBefore(){

this.count++;

this.setState({

display:this.count > 0

});

}

onComplete(){

this.count--;

this.setState({

display:this.count > 0

});

}

render(){

return

}

}

The End。


版权声明:本文为weixin_35379749原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。