javascript - 加载指示器,当所有异步操作完成时显示,并且显示时间最短以获得更好的用户体验
问题描述
当所有异步操作完成时,我有一个加载指示器正确显示。这是使用单独的isFetching
存储来完成的,该存储将根据动作是否仍在加载将动作标记为真或假。
例如,isLoading
下面的 prop 将返回一个包含所有应用程序操作及其当前加载状态的对象,例如:
isLoading: {
SOME_ACTION_FIRST: false,
SOME_ACTION_SECOND: true,
SOME_ACTION_THIRD: false
}
有了它,我可以有条件地显示加载器,仅当并非所有操作都处于某种false
状态时才显示。这很好用,世界上一切都很好。
class ExampleComponentUsingLoader extends Component {
checkForallFalseValues = (obj) => {
return Object.keys(obj).every(function(val){
return obj[val] === false
})
}
render(){
const { isLoading } = this.props
return(
{ !allFalseValues(loading) && <LoadingIndicator /> }
)
}
}
事情变得棘手的地方 - 最小时间加载指示
React 中的用户体验是福也是祸。一方面它真的很快,这让人感觉很神奇。另一方面,它可能太快了,这是一种非常不和谐的体验。
从关于 UX 堆栈交换的帖子中,通常认为 1 秒是用户流程的理想长度:
1 秒让用户的思路顺畅无阻。用户可以感觉到延迟,因此知道计算机正在生成结果,但他们仍然感觉可以控制整体体验,并且他们可以自由移动,而不是在计算机上等待。良好的导航需要这种程度的响应能力。
所以我的目标不仅是根据动作的加载状态来显示指标,还要给它一个最短的显示时间。
我需要考虑的事情:
- 在某些情况下,操作将花费超过 1 秒的时间,在这种情况下,我只想显示该时间量的指示器。
- 在其他情况下,这些操作将花费不到 1 秒的时间,在这种情况下,我需要确保指示器至少显示 1 秒。
我试过的
setTimeout
如果我只想添加一般延迟,我已经尝试过哪种方法有效,但这并不是我的真正目标:
class LoadingIndicator extends React.Component {
constructor(props) {
super(props);
this.enableMessage = this.enableMessage.bind(this);
this.state = {
displayMessage: false,
};
this.timer = setTimeout(this.enableMessage, 250);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
enableMessage() {
this.setState({displayMessage: true});
}
render() {
const {displayMessage} = this.state;
if (!displayMessage) {
return null;
}
return <div>Loading...</div>;
}
}
我正在寻找一些建议,以将这种想法与isLoading
action reducer prop 的实际加载时间结合起来的最佳方式。
解决方案
我开始想出一个相当复杂的解决方案,然后意识到有一个更简单的解决方案。两者都使用承诺。我对 React 或 Promises 没有太多经验,但我认为类似的东西应该会让你接近。我先给出一个简单的:
class ExampleComponentUsingLoader extends Component {
checkForallFalseValues = (obj) => {
return Object.keys(obj).every(function(val){
return obj[val] === false
})
}
constructor(props) {
super(props)
this.delayedPromise = new Promise((resolve, reject) => setTimeout(() => resolve(), 1000))
this.state = { loading: true, delayMet: false }
this.delayedPromise.then(() => this.setState({ delayMet: true }))
}
render(){
const { isLoading } = this.props
if (checkForAllFalseValues(isLoading)) {
this.state.setState({ loading: false });
}
return(
{ !(this.state.delayMet && !this.state.loading) && <LoadingIndicator /> }
)
}
}
这是更复杂的例子。我对 Javascript 类也没有太多经验,所以我为DelayedPromise
该类使用了一个函数,因此我可以确保我的作用域有效:
function DelayedPromise(delay) {
const self = this;
// This is what takes care of the timing, it will resolve after the delay
const timerPromise = new Promise(function(resolve, reject) {
setTimeout(function() { resolve(); }, delay);
});
const resolvablePromise = new Promise(function(resolve, reject) {
self.resolve = resolve;
self.reject = reject;
});
// This gives you the promise functionality.
const proxyPromise = Promise.all([timerPromise, resolvablePromise]);
proxyPromise.resolve = self.resolve;
proxyPromise.reject = self.reject;
return proxyPromise;
}
console.log('start', new Date());
const p = new DelayedPromise(1000);
p.then(function() { console.log('resolved', new Date()); });
p.resolve();
以下是如何使用它的可能性:
class ExampleComponentUsingLoader extends Component {
checkForallFalseValues = (obj) => {
return Object.keys(obj).every(function(val){
return obj[val] === false
})
}
constructor(props) {
super(props)
this.delayedPromise = new DelayedPromise(1000)
this.state = { loading: true }
this.delayedPromise.then(() => this.setState({ loading: false }))
}
render(){
const { isLoading } = this.props
if (checkForAllFalseValues(isLoading)) {
this.delayedPromise.resolve();
}
return(
{ this.state.loading && <LoadingIndicator /> }
)
}
}
推荐阅读
- django - Django:用变量翻译messages.success
- python - pip安装导致RecursionError
- c# - 为什么要将公共异步方法的内部逻辑分离到私有方法中?
- python - Oracle - django.db.utils.DatabaseError: ORA-01722: 无效号码
- ajax - MVC 5 控制器中没有自定义/授权标头
- postgresql - 如何使用 apollo / graphql 实现节点查询解析器
- c# - Asp Net Core 数据绑定中的小数点分隔符问题
- django - 将参数传递给已从 Django 视图的模板中接收到的其他 url
- node.js - 带有 nodejs 和 ejs 的文档中的电子签名
- html - 如何更好地显示卡片内的元素?