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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35456935/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 17:45:17  来源:igfitidea点击:

How to show a loading indicator in React Redux app while fetching the data?

javascriptreactjsredux

提问by u1213150

I'm new to React/Redux. I use a fetch api middleware in Redux app to process the APIs. It's (redux-api-middleware). I think it's the good way to process async api actions. But I find some cases which can't be resolve by myself.

我是 React/Redux 的新手。我在 Redux 应用程序中使用 fetch api 中间件来处理 API。它是(redux-api-middleware)。我认为这是处理异步 api 操作的好方法。但是我发现有些情况是我自己无法解决的。

As the homepage (Lifecycle) say, a fetch API lifecycle begins with dispatching a CALL_API action ends with dispatching a FSA action.

正如主页 ( Lifecycle) 所说,获取 API 生命周期以调度 CALL_API 操作开始,以调度 FSA 操作结束。

So my first case is showing/hiding a preloader when fetching APIs. The middleware will dispatch a FSA action at the beginning and dispatch a FSA action at the end. Both the actions are received by reducers which should be only doing some normal data processing. No UI operations, no more operations. Maybe I should save the processing status in state then render them when store updating.

所以我的第一个案例是在获取 API 时显示/隐藏预加载器。中间件将在开始时分派 FSA 操作,并在结束时分派 FSA 操作。这两个动作都被减速器接收,它应该只做一些正常的数据处理。没有 UI 操作,没有更多的操作。也许我应该将处理状态保存在 state 中,然后在商店更新时呈现它们。

But how to do this? A react component flow over the whole page? what happen with store updating from other actions? I mean they are more like events than state!

但是如何做到这一点呢?整个页面上的反应组件流?从其他操作更新商店会发生什么?我的意思是它们更像是事件而不是状态!

Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?

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

Best wishes! Wish for replying.

最好的祝愿!望回复。

回答by Dan Abramov

I mean they are more like events than state!

我的意思是它们更像是事件而不是状态!

I would not say so. I think loading indicators are a great case of UI that is easily described as a function of state: in this case, of a boolean variable. While this answeris correct, I would like to provide some code to go along with it.

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

In the asyncexample in Redux repo, reducer updates a field called isFetching:

asyncRedux repo示例中,reducer更新了一个名为 的字段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

The component uses connect()from React Redux to subscribe to the store's state and returns isFetchingas part of the mapStateToProps()return valueso it is available in the connected component's props:

该组件使用connect()从阵营终极版订阅商店的状态和收益isFetching作为的一部分mapStateToProps()返回值,以便它在连接组件的道具可供选择:

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

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

Finally, the component uses isFetchingprop in the render()functionto render a “Loading...” label (which could conceivably be a spinner instead):

最后,该组件函数中使用isFetchingproprender()来渲染“Loading...”标签(可以想象它可以是一个微调器):

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

Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?

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

Any side effects (and showing a dialog is most certainly a side effect) do not belong in reducers. Think of reducers as passive “builders of state”. They don't really “do” things.

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

If you wish to show an alert, either do this from a component before dispatching an action, or do this from an action creator. By the time an action is dispatched, it is too late to perform side effects in response to it.

如果您希望显示警报,请在分派操作之前从组件执行此操作,或者从操作创建者执行此操作。当一个动作被分派时,执行副作用来响应它已经太晚了。

For every rule, there is an exception. Sometimes your side effect logic is so complicated you actually wantto couple them either to specific action types or to specific reducers. In this case check out advanced projects like Redux Sagaand Redux Loop. Only do this when you are comfortable with vanilla Redux and have a real problem of scattered side effects you'd like to make more manageable.

对于每个规则,都有一个例外。有时您的副作用逻辑非常复杂,您实际上想要将它们耦合到特定的操作类型或特定的减速器。在这种情况下,请查看Redux SagaRedux Loop等高级项目。仅当您对 vanilla Redux 感到满意并且有分散副作用的真正问题时才这样做,您希望使其更易于管理。

回答by Nuno Campos

Great answer Dan Abramov! Just want to add that I was doing more or less exactly that in one of my apps (keeping isFetching as a boolean) and ended up having to make it an integer (which ends up reading as the number of outstanding requests) to support multiple simultaneous requests.

很好的答案丹·阿布拉莫夫!只是想补充一点,我在我的一个应用程序中或多或少地做这件事(将 isFetching 保持为布尔值),最终不得不将其设为整数(最终读取为未完成请求的数量)以支持多个同时要求。

with boolean:

带布尔值:

request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> spinner off -> request 2 ends

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

with integer:

带整数:

request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> request 2 ends -> spinner off

请求 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

回答by javivelasco

I'd like to add something. The real world example uses a field isFetchingin the store to represent when a collectionof items is being fetched. Any collection is generalized to a paginationreducer that can be connected to your components to track the state and show if a collection is loading.

我想补充一点。现实世界的示例使用isFetching商店中的一个字段来表示何时提取项目集合。任何集合都被概括为一个paginationreducer,它可以连接到您的组件以跟踪状态并显示集合是否正在加载。

It happened to me that I wanted to fetch details for an specific entity that doesn't fit in the pagination pattern. I wanted to have a state representing if the details are being fetched from the server but also I didn't want to have a reducer just for that.

我碰巧想获取不适合分页模式的特定实体的详细信息。我想要一个状态来表示是否从服务器获取详细信息,但我也不想为此使用减速器。

To solve this I added another generic reducer called fetching. It works in a similar fashion to the pagination reducer and it's responsibility is just to watcha set of actions and generate new state with pairs [entity, isFetching]. That allows to connectthe reducer to any component and to know if the app is currently fetching data not just for a collection but for an specific entity.

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

回答by bright-star

I didn't happen upon this question until now, but since no answer is accepted I'll throw in my hat. I wrote a tool for this very job: react-loader-factory. It's got slightly more going on than Abramov's solution, but is more modular and convenient, since I didn't want to have to think after I wrote it.

直到现在我才遇到这个问题,但由于没有接受任何答案,我会认输。我为这项工作编写了一个工具:react-loader-factory。它比 Abramov 的解决方案稍微多一点,但更加模块化和方便,因为我不想在写完之后再考虑。

There are four big pieces:

有四大块:

  • Factory pattern: This allows you to quickly call the same function to set up which states mean "Loading" for your component, and which actions to dispatch. (This assumes that the component is responsible for starting the actions it waits on.) const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Wrapper: The component the Factory produces is a "higher order component" (like what connect()returns in Redux), so that you can just bolt it onto your existing stuff. const LoadingChild = loaderWrapper(ChildComponent);
  • Action/Reducer interaction: The wrapper checks to see if a reducer it's plugged into contains keywords that tell it not to pass through to the component that needs data. The actions dispatched by the wrapper are expected to produce the associated keywords (the way redux-api-middleware dispatches ACTION_SUCCESSand ACTION_REQUEST, for example). (You could dispatch actions elsewhere and just monitor from the wrapper if you wanted, of course.)
  • Throbber: The component you want to appear while the data your component depends on isn't ready. I added a little div in there so you can test it out without having to rig it up.
  • 工厂模式:这允许您快速调用相同的函数来为您的组件设置哪些状态意味着“正在加载”,以及要调度哪些操作。(这假设组件负责启动它等待的操作。)const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • 包装器:工厂生产的组件是一个“高阶组件”(就像connect()Redux 中返回的组件),因此您可以将其固定在现有的东西上。const LoadingChild = loaderWrapper(ChildComponent);
  • Action/Reducer 交互:包装器检查它插入的减速器是否包含告诉它不要传递到需要数据的组件的关键字。包装器调度的操作预计会产生相关的关键字(例如 redux-api-middleware 调度ACTION_SUCCESS和的方式ACTION_REQUEST)。(当然,如果您愿意,您可以在其他地方分派动作并从包装器中进行监控。)
  • Throbber:当你的组件所依赖的数据还没有准备好时,你想要出现的组件。我在那里添加了一个小 div,这样你就可以测试它而无需安装它。

The module itself is independent of redux-api-middleware, but that's what I use it with, so here's some sample code from the README:

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

A component with a Loader wrapping it:

一个带有 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 } />;
}

A reducer for the Loader to monitor (although you can wire it differentlyif you want):

Loader 监控的减速器(尽管您可以根据需要对其进行不同的接线):

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;
}

I expect in the near future I'll add things like timeout and error to the module, but the pattern's not going to be very different.

我希望在不久的将来我会向模块添加诸如超时和错误之类的内容,但模式不会有很大不同。



The short answer to your question is:

你的问题的简短回答是:

  1. Tie rendering to rendering code--use a wrapper around the component you need to render with the data like the one I showed above.
  2. Add a reducer that makes the status of requests around the app you might care about easily digestible, so you don't have to think too hard about what is happening.
  3. Events and state aren't really different.
  4. The rest of your intuitions seem correct to me.
  1. 将渲染与渲染代码联系起来——在需要渲染数据的组件周围使用一个包装器,就像我上面展示的那样。
  2. 添加一个reducer,使您可能关心的应用程序周围的请求状态易于理解,因此您不必考虑正在发生的事情。
  3. 事件和状态并没有真正的不同。
  4. 你的其他直觉对我来说似乎是正确的。

回答by Spock

Am I the only one thinking that loading indicators don't belong in a Redux store? I mean, I don't think it's part of an application's state per se..

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

Now, I work with Angular2, and what I do is that I have a "Loading" service which exposes different loading indicators via RxJS BehaviourSubjects.. I guess the mechanism is the same, I just don't store the information in Redux.

现在,我使用 Angular2,我所做的是我有一个“加载”服务,它通过 RxJS BehaviourSubjects 公开不同的加载指标。我猜机制是一样的,我只是不将信息存储在 Redux 中。

Users of the LoadingService just subscribe to those events they want to listen to..

LoadingService 的用户只需订阅他们想要收听的事件。

My Redux action creators call the LoadingService whenever things need to change. UX components subscribe to the exposed observables...

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

回答by Dreculah

We have three types of notifications in our app, all of which are designed as aspects:

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

  1. Loading indicator (modal or non-modal based on prop)
  2. Error Popup (modal)
  3. Notification snackbar (non-modal, self closing)
  1. 加载指示器(基于道具的模态或非模态)
  2. 错误弹出窗口(模态)
  3. 通知小吃店(非模态,自关闭)

All three of these are at the top level of our app (Main), and wired through Redux as shown in the below code snippet. These props control display of their corresponding aspects.

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

I designed a proxy that handles all our API calls, thus all isFetching and (api) errors are mediated with actionCreators I import in the proxy. (As an aside, I also use webpack to inject a mock of the backing service for dev so we can work without server dependencies.)

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

Any other place in the app that needs to provide any type of notification simply imports the appropriate action. Snackbar & Error have params for messages to be displayed.

应用程序中需要提供任何类型通知的任何其他位置只需导入适当的操作。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 extends React.Component{

) 导出默认类 Main 扩展 React.Component{

回答by Milo? Ra?i?

You can add change listeners to your stores, using either connect()from React Redux or the low-level store.subscribe()method. You should have the loading indicator in your store, which the store change handler can then check and update the component state. The component then renders the preloader if needed, based on the state.

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

alertand confirmshouldn't be a problem. They are blocking and alert doesn't even take any input from the user. With confirm, you can set state based on what the user has clicked if the user choice should affect component rendering. If not, you can store the choice as component member variable for later use.

alert并且confirm不应该是一个问题。他们正在阻止并且警报甚至不接受用户的任何输入。使用confirm,如果用户选择会影响组件渲染,您可以根据用户单击的内容设置状态。如果没有,您可以将选择存储为组件成员变量以供以后使用。

回答by Sergiu

I'm saving the urls such as::

我正在保存网址,例如::

isFetching: {
    /api/posts/1: true,
    api/posts/3: false,
    api/search?q=322: true,
}

And then I have a memorised selector (via reselect).

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

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

To make the url unique in case of POST, I pass some variable as query.

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

And where I want to show an indicator, I simply use the getFetchCount variable

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