如何在获取数据时在React Redux应用程序中显示加载指示器?

时间:2016-02-17 12:36:48

标签: javascript reactjs redux

我是React / Redux的新手。我在Redux应用程序中使用fetch api中间件来处理API。这是https://github.com/agraboso/redux-api-middleware。我认为这是处理异步api操作的好方法。但我发现有些案例是我自己无法解决的。

正如主页https://github.com/agraboso/redux-api-middleware#lifecycle所说,fetch API生命周期开始于调度CALL_API操作,最后调度FSA操作。

所以我的第一个案例是在获取API时显示/隐藏预加载器。中间件将在开始时发送FSA操作,并在最后发送FSA操作。这两个动作都是由减速器接收的,减速器应该只进行一些正常的数据处理。没有UI操作,没有更多操作。也许我应该将处理状态保存在状态,然后在存储更新时渲染它们。

但是怎么做?一个反应组件流过整个页面?从其他操作更新商店会发生什么?我的意思是他们更像是事件而非国家!

即使是更糟糕的情况,当我必须在redux / react应用程序中使用本机确认对话框或警报对话框时,我该怎么办?它们应该放在哪里,行动还是减少?

祝福!希望回复。

8 个答案:

答案 0 :(得分:146)

  

我的意思是他们更像是事件而非国家!

我不会这么说。我认为加载指标是UI的一个很好的例子,很容易被描述为状态的函数:在这种情况下,是一个布尔变量。虽然this answer是正确的,但我想提供一些代码来配合它。

async example in Redux repo,reducer updates a field called isFetching

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: true,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: false,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

该组件使用React Redux中的connect()来订阅商店的状态和returns isFetching as part of the mapStateToProps() return value,因此它在连接组件的道具中可用:

function mapStateToProps(state) {
  const { selectedReddit, postsByReddit } = state
  const {
    isFetching,
    lastUpdated,
    items: posts
  } = postsByReddit[selectedReddit] || {
    isFetching: true,
    items: []
  }

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

最后,组件uses isFetching prop in the render() function呈现“正在加载...”标签(可以想象它是一个微调器):

{isEmpty
  ? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
  : <div style={{ opacity: isFetching ? 0.5 : 1 }}>
      <Posts posts={posts} />
    </div>
}
  

即使是更糟糕的情况,当我必须在redux / react应用程序中使用本机确认对话框或警报对话框时,我该怎么办?它们应该放在哪里,行动还是减少?

任何副作用(并显示对话框肯定是副作用)不属于reducer。将减速器视为被动的“国家建设者”。他们并没有真正“做”事情。

如果您希望显示警报,请在发送操作之前从组件执行此操作,或者从操作创建者执行此操作。在调度动作时,为了响应它而执行副作用为时已晚。

对于每条规则,都有例外。有时你的副作用逻辑是如此复杂,你实际上想要将它们耦合到特定的动作类型或特定的减速器。在这种情况下,请查看Redux SagaRedux Loop等高级项目。只有当你对香草Redux感到舒服并且有一个真正的分散副作用问题时才能做到这一点,你想让它更易于管理。

答案 1 :(得分:19)

伟大的回答丹阿布拉莫夫! 只是想补充说我在我的一个应用程序中做了或多或少的操作(将isFetching保留为布尔值)并最终必须使其成为一个整数(最终读取为未完成请求的数量)以支持多个同时请求。

with boolean:

请求1开始 - &gt;旋转器 - &gt;请求2开始 - &gt;请求1结束 - &gt;旋转器 - &gt;请求2结束

带整数:

请求1开始 - &gt;旋转器 - &gt;请求2开始 - &gt;请求1结束 - &gt;请求2结束 - &gt;旋转器

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching + 1,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching - 1,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

答案 2 :(得分:12)

我想补充一些东西。真实世界示例使用商店中的字段isFetching来表示何时提取项目的集合。任何集合都推广到pagination缩减器,可以连接到组件以跟踪状态并显示集合是否正在加载。

我想要获取不适合分页模式的特定实体的详细信息。我希望有一个状态来表示是否从服务器获取了详细信息,但我也不想为此设置一个reducer。

为了解决这个问题,我添加了另一个名为fetching的通用缩减器。它的工作方式与分页缩减器类似,它的职责是观察一组动作,并使用对[entity, isFetching]生成新状态。这允许connect reducer到任何组件,并知道app当前是否正在为一个集合而不是为一个特定实体提取数据。

答案 3 :(得分:5)

直到现在我才发现这个问题,但由于没有接受答案,我会戴上帽子。我为这项工作写了一个工具:react-loader-factory。它比阿布拉莫夫的解决方案稍微强一些,但更加模块化和方便,因为我不想在写完之后再去思考。

有四件大事:

  • 工厂模式:这允许您快速调用相同的函数来设置哪些状态意味着组件的“加载”以及要分派的操作。 (这假定组件负责启动它等待的操作。)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Wrapper:Factory生成的组件是一个“高阶组件”(就像Redux中connect()返回的那样),这样你就可以把它固定在你现有的东西上。 const LoadingChild = loaderWrapper(ChildComponent);
  • Action / Reducer交互:包装器检查它插入的reducer是否包含告诉它不要传递给需要数据的组件的关键字。期望由包装器调度的动作产生关联的关键字(例如,redux-api-middleware调度ACTION_SUCCESSACTION_REQUEST的方式)。 (当然,您可以在其他地方发送操作,只需从包装器进行监控。)
  • Throbber:您希望在组件所依赖的数据时显示的组件尚未就绪。我在那里添加了一个小div,所以你可以测试它而不需要进行操作。

模块本身独立于redux-api-middleware,但这就是我使用它,所以这里是README的一些示例代码:

包含Loader的组件:

import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';

const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);

const LoadingChild = loaderWrapper(ChildComponent);

const containingComponent = props => {
  // Do whatever you need to do with your usual containing component 

  const childProps = { someProps: 'props' };

  return <LoadingChild { ...childProps } />;
}

要加载的加载器的缩减器(尽管如果需要,可以wire it differently):

export function activeRequests(state = [], action) {
  const newState = state.slice();

  // regex that tests for an API action string ending with _REQUEST 
  const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
  // regex that tests for a API action string ending with _SUCCESS 
  const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);

  // if a _REQUEST comes in, add it to the activeRequests list 
  if (reqReg.test(action.type)) {
    newState.push(action.type);
  }

  // if a _SUCCESS comes in, delete its corresponding _REQUEST 
  if (sucReg.test(action.type)) {
    const reqType = action.type.split('_')[0].concat('_REQUEST');
    const deleteInd = state.indexOf(reqType);

    if (deleteInd !== -1) {
      newState.splice(deleteInd, 1);
    }
  }

  return newState;
}

我预计在不久的将来,我会在模块中添加超时和错误等内容,但模式不会有太大差异。

您问题的简短回答是:

  1. 将渲染与渲染代码联系起来 - 使用您需要使用上面显示的数据渲染的组件周围的包装。
  2. 添加一个缩减器,使您可能关心的应用程序状态变得易于理解,因此您不必过于考虑正在发生的事情。
  3. 事件和状态并没有什么不同。
  4. 你的其他直觉对我来说似乎是正确的。

答案 4 :(得分:3)

您可以使用React Redux中的connect()或低级store.subscribe()方法向商店添加更改侦听器。您应该在商店中有加载指示器,然后商店更改处理程序可以检查并更新组件状态。然后,组件根据状态呈现预加载器。

alertconfirm不应成为问题。它们是阻塞的,警报甚至不会从用户那里获得任何输入。使用confirm,如果用户选择应影响组件呈现,则可以根据用户单击的内容设置状态。如果没有,您可以将选项存储为组件成员变量以供以后使用。

答案 5 :(得分:3)

我们的应用程序中有三种类型的通知,所有通知都设计为方面:

  1. 装载指示器(基于道具的模态或非模态)
  2. 错误弹出窗口(模态)
  3. 通知零食店(非模态,自我关闭)
  4. 所有这三个都位于我们的应用程序(Main)的顶层,并通过Redux连接,如下面的代码片段所示。这些道具控制着相应方面的显示。

    我设计了一个处理所有API调用的代理,因此所有isFetching和(api)错误都是由我在代理中导入的actionCreators调解的。 (顺便说一句,我也使用webpack为dev注入一个支持服务的模拟,这样我们就可以在没有服务器依赖的情况下工作。)

    应用中需要提供任何类型通知的任何其他位置只需导入相应的操作即可。 Snackbar&amp;错误具有要显示的消息的参数。

    @connect(
    // map state to props
    state => ({
        isFetching      :state.main.get('isFetching'),   // ProgressIndicator
        notification    :state.main.get('notification'), // Snackbar
        error           :state.main.get('error')         // ErrorPopup
    }),
    // mapDispatchToProps
    (dispatch) => { return {
        actions: bindActionCreators(actionCreators, dispatch)
    }}
    

    ) export default class Main扩展了React.Component {

答案 6 :(得分:3)

我是唯一一个认为加载指标不属于Redux商店的人吗?我的意思是,我不认为它本身就是应用程序的一部分......

现在,我使用Angular2,我所做的就是我有一个&#34; Loading&#34;通过RxJS BehaviourSubjects暴露不同加载指标的服务..我猜机制是一样的,我只是不把信息存储在Redux中。

LoadingService的用户只是订阅他们想要收听的事件..

我的Redux操作创建者会在需要更改时调用LoadingService。 UX组件订阅了公开的observables ......

答案 7 :(得分:1)

我正在保存诸如::

之类的网址
isFetching: {
    /api/posts/1: true,
    api/posts/3: false,
    api/search?q=322: true,
}

然后我有一个记忆选择器(通过重新选择)。

const getIsFetching = createSelector(
    state => state.isFetching,
    items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);

要在POST的情况下使url唯一,我将一些变量作为查询传递。

在我想要显示指标的地方,我只是使用getFetchCount变量