首页 文章

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

提问于
浏览
89

我'm new to React/Redux. I use a fetch api middleware in Redux app to process the APIs. It' s https://github.com/agraboso/redux-api-middleware . 我认为这是我自己解决的问题 .

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

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

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

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

最好的祝愿!希望回复 .

8 回答

  • 136

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

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

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

  • 12

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

    alertconfirm 甚至不应该从用户那里获取任何输入 . 使用 confirm ,如果用户选择应影响组件渲染,则可以根据用户单击的内容设置状态 . 如果没有,您可以将选择存储为组件成员变量以供以后使用 .

  • 1

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

    • 装载指示器(基于道具的模态或非模态)

    • 错误弹出窗口(模态)

    • 通知零食栏(非模态,自动关闭)

    所有这三个都在我们的应用程序(主要)的顶层,并通过Redux连接,如下面的代码片段所示 . 这些道具控制其相应方面的显示 .

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

    应用程序中需要提供任何类型通知的任何其他位置只需导入相应的操作 . Snackbar&Error具有要显示的消息的参数 .

    @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 {

  • 5

    我正在保存诸如::

    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变量

  • 3

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

    现在,我使用Angular2,我所做的是我有一个“加载”服务,它通过RxJS BehaviourSubjects暴露不同的加载指示器 . 我想机制是相同的,我只是不存储Redux中的信息 .

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

    每当事情需要改变时,我的Redux动作创建者就会调用LoadingService . UX组件订阅公开的observables ......

  • 3

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

    用布尔值:

    请求1开始 - >旋转器开启 - >请求2开始 - >请求1结束 - >旋转器关闭 - >请求2结束

    带整数:

    请求1开始 - >旋转器开启 - >请求2开始 - >请求1结束 - >请求2结束 - >请求者关闭

    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
    
  • 3

    我没't happen upon this question until now, but since no answer is accepted I'我会戴上帽子 . 我为这份工作写了一个工具:react-loader-factory . 它是_175471的解决方案,但更加模块化和方便,因为我写完之后不想再去思考 .

    有四大块:

    • 工厂模式:这允许您快速调用相同的函数来设置组件的哪些状态"Loading"以及要分派的操作 . (这假定组件负责启动它等待的操作 . ) const loaderWrapper = loaderFactory(actionsList, monitoredStates);

    • Wrapper:Factory生成的组件是"higher order component"(就像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 } />;
    }
    

    要监控的Loader的减速器(尽管如果你愿意,你可以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;
    }
    

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


    你问题的简短回答是:

    • 将渲染与渲染代码联系起来 - 使用您需要使用上面显示的数据渲染的组件周围的包装器 .

    • 添加一个减少器,使您可能关心的应用程序的状态容易消化,因此您不必过于考虑正在发生的事情 .

    • 事件和状态并没有什么不同 .

    • 你的其他直觉对我来说似乎是正确的 .

  • 19

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

    我不会这么说 . 我认为加载指标是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,因此它在连接组件的props中可用:

    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感到舒服并且有一个真正的分散副作用问题时才能做到这一点,你想让它更易于管理 .

相关问题