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 ?
{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 ?
}
}
经过上面的改造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 ?
}
}
严格来说BlockLoading组件用 onShow, onHide 比使用 onSendBefore,onComplete着两个事件名命更好。后者是ajax的时间,前者才是UI组件的事件。如下是否更好
BlockLoading组件
import React from 'react'
class BlockLoading extends React.Component{
render(){
return this.props.display ?
}
}
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。