【问题标题】:How to delay (e.g. 1s) the showing of a loading indicator in React Redux Saga App?如何延迟(例如 1 秒)在 React Redux Saga App 中显示加载指示器?
【发布时间】:2017-07-31 22:55:19
【问题描述】:

我正在使用 reducer 来检查各种操作状态,如成功、待处理、错误等。我想在延迟 1 秒后显示加载指示器。如果响应在 1 秒之前出现,那么我不想显示加载指示器。

目前,我没有更新挂起状态的加载状态,而是使用 setTimeout 从渲染函数触发操作。

这会在超时期限之前交付响应时产生问题。我该如何解决这个问题?

reducer.js:

const initialState = {
  error: false,
  loading: false,
  showModal: false,
};

export default function appReducer(state=initialState, action) {

  if (action.type.endsWith('ERROR'))
    return {
      ...state,
      error: true,
      loading: false,
      showModal: true,
    };
  else if (action.type.endsWith('PENDING'))
    return {
      ...state,
      error: false,
      loading: false,
    };
  else if (action.type.endsWith('SUCCESS'))
      return {
        ...state,
        error: false,
        loading: false,
      };
  else if (action.type === errorModalActionTypes.CLOSE_MODAL.ACTION)
    return {
      ...state,
      showModal: false,
    };
  else if (action.type === loadingIndicatorActionTypes.UPDATE_LOADING.ACTION)
    return {
      ...state,
      loading: true,
    };
  else
    return state;
}

saga.js

export function* getCollections(action) {
  try {
    yield put({ type: GET_COLLECTIONS.PENDING });
    const collections = yield call(getCollectionsAPI);
    yield put({ type: GET_COLLECTIONS.SUCCESS, collections });
  } catch (error) {
    yield put({ type: GET_COLLECTIONS.ERROR, error });
  }
}

// These are the watchers that trigger the start of a saga

export default function* saga() {
  yield fork(takeEvery, GET_COLLECTIONS.ACTION, getCollections);
}

LoadingIndicator.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

import { isLoading, hasError } from './selectors';
import { updateLoading } from './actions';
import LoadingIndicatorComponent  from '../../../../components/loadingIndicator';
import './loadingIndicator.css';

export class LoadingIndicator extends Component {

  render() {
    console.log('called');
    const { loading, error } = this.props;

    if (!error)
      setTimeout(this.props.updateLoading, 1000);

    return (
      <div className={`${loading && !error ? 'show' : 'hidden'}`}>
        <LoadingIndicatorComponent>
          Loading...
        </LoadingIndicatorComponent>
      </div>
    );
  }

}

const mapStateToProps = (state) => ({
  loading: isLoading(state),
  error: hasError(state),
});

export default connect(mapStateToProps, { updateLoading })(LoadingIndicator);

【问题讨论】:

    标签: javascript reactjs redux react-redux redux-saga


    【解决方案1】:

    鉴于您的架构,我的建议是:

    export class LoadingIndicator extends Component {
    
      constructor(props) {
        super(props);
        this.timeoutID = null;
        this.state = {
          showIndicator: false,
        };
      }
    
      componentDidMount() {
        this.ensureTimer(this.props);
      }
    
      componentWillUnmount() {
        this.destroyTimer();
      }
    
      componentWillReceiveProps(props) {
        if (props.loading !== this.props.loading || props.error !== this.props.error) {
          this.ensureTimer(props);
        }
      }
    
      ensureTimer(props) {
        if (props.loading && !props.error) {
          if (!this.timeoutID) {
            this.timeoutID = setTimeout(() => {
              this.timeoutID = null;
              this.setState({showIndicator: true });
            }, 1000);
          }
        } else {
          this.destroyTimer();
        }
      }
    
      destroyTimer() {
        clearTimeout(this.timeoutID);
        this.timeoutID = null;
        this.setState({showIndicator: false });
      }
    
      render() {
        const { loading, error } = this.props;
        return (
          <div className={`${this.state.showIndicator ? 'show' : 'hidden'}`}>
            <LoadingIndicatorComponent>
              Loading...
            </LoadingIndicatorComponent>
          </div>
        );
      }
    }
    
    const mapStateToProps = (state) => ({
      loading: isLoading(state),
      error: hasError(state),
    });
    
    export default connect(mapStateToProps, { updateLoading })(LoadingIndicator);
    

    这里有几点需要注意:

    1. 如果不出意外,请退出这个答案,render() 应该是一个纯函数。它不应该改变任何状态。相反,给定相同的道具和状态,它应该每次都返回相同的东西(并且没有设置副作用)
    2. 相反,我所做的选择是通过 showIndicator 使用状态。在另一个世界中,您可以将逻辑包装在父容器中,但这是让示例更简单的方法。
    3. 这背后的主要逻辑如下:当组件第一次挂载时,或者每当道具改变时,看看是否应该出现加载对话框。这是通过 setTimeout 完成的,然后只更新 showIndicator 标志。如果条件不再成立,则销毁计时器,并将 showIndicator 设置为 false。
    4. 我会阅读其他 react 生命周期钩子,以确保你需要监听所有这些不同事件的原因:https://facebook.github.io/react/docs/react-component.html

    【讨论】:

    • 在第 2 点中,您已经提到将逻辑包装在父容器中。当前代码是一个智能容器组件,其中包括“LoadingIndicatorComponent”哑组件。您的意思是在“LoadingIndicator”的父容器组件中包含逻辑吗?为什么会有帮助?
    • 啊,鉴于你所说的,那么给出示例的方式将是正确的方式。
    猜你喜欢
    • 1970-01-01
    • 2021-07-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-16
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多