首页 > 解决方案 > 加载指示器,当所有异步操作完成时显示,并且显示时间最短以获得更好的用户体验

问题描述

当所有异步操作完成时,我有一个加载指示器正确显示。这是使用单独的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 秒让用户的思路顺畅无阻。用户可以感觉到延迟,因此知道计算机正在生成结果,但他们仍然感觉可以控制整体体验,并且他们可以自由移动,而不是在计算机上等待。良好的导航需要这种程度的响应能力。

https://ux.stackexchange.com/questions/104606/should-a-loading-text-or-spinner-stay-a-minimum-time-on-screen#answer-104782

所以我的目标不仅是根据动作的加载状态来显示指标,还要给它一个最短的显示时间

我需要考虑的事情:

  1. 在某些情况下,操作将花费超过 1 秒的时间,在这种情况下,我只想显示该时间量的指示器。
  2. 在其他情况下,这些操作将花费不到 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>;
  }
}

我正在寻找一些建议,以将这种想法与isLoadingaction reducer prop 的实际加载时间结合起来的最佳方式。

标签: javascriptreactjsreact-redux

解决方案


我开始想出一个相当复杂的解决方案,然后意识到有一个更简单的解决方案。两者都使用承诺。我对 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 /> }
    )
  }
}

推荐阅读