如何在读取数据时在React Redux应用程序中显示加载指示器?
我是React / Redux的新手。 我在Redux应用程序中使用获取api中间件来处理API。 它是https://github.com/agraboso/redux-api-middleware 。 我认为这是处理asynchronousAPI行为的好方法。 但是我发现了一些自己无法解决的情况。
正如主页https://github.com/agraboso/redux-api-middleware#lifecycle所言,抓取API生命周期从调度CALL_API动作开始,然后调度FSA动作。
所以我的第一个例子是在读取API时显示/隐藏预加载器。 中间件将在开始时调度FSA操作,并在最后调度FSA操作。 这两个动作都是由reducer接收的,只应该做一些正常的数据处理。 没有UI操作,没有更多的操作。 也许我应该保存在状态的处理状态,然后在商店更新时呈现它们。
但如何做到这一点? 一个反应组件在整个页面上stream动? 从其他操作更新商店会发生什么情况? 我的意思是说他们比国家更像事件!
即使是更糟糕的情况,当我必须使用redux / react应用程序中的本机确认对话框或警报对话框时,我该怎么办? 他们应该放在哪里,行动还是减less?
最好的祝愿! 希望回复。
我的意思是说他们比国家更像事件!
我不会这么说。 我认为加载指标是UI的一个很好的例子,很容易描述为状态的函数:在这种情况下,是一个布尔variables。 虽然这个答案是正确的,但我想提供一些代码去处理它。
在Redux isFetching
的async
示例中 ,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
该组件使用React Redux中的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 } }
最后,组件在render()
函数中使用isFetching
prop来呈现一个“Loading …”标签(可以想象它是一个微调isFetching
)。
{isEmpty ? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>) : <div style={{ opacity: isFetching ? 0.5 : 1 }}> <Posts posts={posts} /> </div> }
即使是更糟糕的情况,当我必须使用redux / react应用程序中的本机确认对话框或警报对话框时,我该怎么办? 应该把他们放在哪里,行动还是减less?
任何副作用(并显示对话肯定是副作用)不属于reducer。 认为减速机是被动的“国家build设者”。 他们并不真正“做”的事情。
如果您希望显示警报,请在分派操作之前从组件执行此操作,或者从操作创build者执行此操作。 在发布行动的时候,为了回应这个问题,执行副作用为时已晚。
对于每一条规则,都有一个例外。 有时候,你的副作用逻辑如此复杂,你实际上想把它们与特定的动作types或特定的reducer结合起来。 在这种情况下,请查看Redux Saga和Redux Loop等高级项目。 只有当你对香草Redux感到舒适时,才能做到这一点,并有一个真正的问题,散落的副作用,你想更容易pipe理。
我想添加一些东西。 现实世界中的示例使用商店中的一个字段“ isFetching
”来表示正在提取项目集合的时间。 任何集合被推广到一个pagination
器,可以连接到您的组件跟踪状态,并显示是否加载一个集合。
我碰巧想要获取不适合分页模式的特定实体的详细信息。 我想拥有一个状态来表示是否从服务器获取细节,但是我也不想只为了这个而使用reducer。
为了解决这个问题,我添加了一个名为fetching
通用fetching
。 它与分页缩减器的工作方式类似,只是要观察一组动作并使用pair [entity, isFetching]
生成新的状态。 这允许connect
reducer connect
到任何组件,并知道该应用程序当前是否正在提取数据,而不仅仅是一个集合,而是一个特定的实体。
很好的答案Dan Abramov! 只是想补充说,我正在做一个或多或less正是在我的一个应用程序(保持作为一个布尔值),并最终不得不使其成为一个整数(最终读为未完成的请求的数量),以支持多个同时要求。
用布尔值:
请求1开始 – >微调开启 – >请求2开始 – >请求1结束 – >微调closures – >请求2结束
整数:
请求1开始 – >微调开启 – >请求2开始 – >请求1结束 – >请求2结束 – >微调closures
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
直到现在我还没有发生这个问题,但是由于没有答案被接受,所以我会抛出我的帽子。 我为这个工作写了一个工具: react-loader-factory 。 它比阿布拉莫夫的解决scheme稍微复杂一些,但更加模块化和方便,因为我不想在写完之后思考。
有四大块:
- 工厂模式:这使您可以快速调用相同的function来设置组件的“装载”状态,以及分派哪些操作。 (这里假定组件负责启动等待的动作。)
const loaderWrapper = loaderFactory(actionsList, monitoredStates);
- 包装:工厂生产的组件是一个“更高级的组件”(比如Redux中的
connect()
返回的内容),这样你就可以将其绑定到你现有的东西上。const LoadingChild = loaderWrapper(ChildComponent);
- Action / Reducer交互:包装器检查是否有插入的reducer包含关键字,告诉它不要传递给需要数据的组件。 包装器派发的动作需要产生关联的关键字(例如,
ACTION_REQUEST
-api-middleware分派ACTION_SUCCESS
和ACTION_REQUEST
)。 (当然,如果你想的话,你也可以在其他地方调度动作,只需要从包装器中监控)。 - Throbber:当组件依赖的数据尚未准备就绪时,您想要显示的组件。 我在那里添加了一个小的div,所以你可以testing一下,而不需要进行修改。
模块本身独立于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 } />; }
装载机的监视器(尽pipe你可以用不同的方式连接 ):
export function activeRequests(state = [], action) { const newState = state.slice(); // regex that tests for an API action string ending with _REQUEST const reqReg = new RegExp(/^[AZ]+\_REQUEST$/g); // regex that tests for a API action string ending with _SUCCESS const sucReg = new RegExp(/^[AZ]+\_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; }
我预计在不久的将来,我会添加诸如超时和错误等模块,但模式不会有很大的不同。
你的问题的简短答案是:
- 将渲染绑定到渲染代码 – 在需要渲染的组件上使用一个包装器,像上面展示的那样。
- 添加一个减速器,使您可能关心易于消化的应用程序的请求状态,所以您不必过多地考虑发生的事情。
- 事件和状态并没有真正的不同。
- 其余的直觉对我来说似乎是正确的。
您可以使用React Redux中的connect()
或低级store.subscribe()
方法将更改侦听器添加到您的存储中。 您应该在商店中加载指示器,然后商店更改处理程序可以检查和更新组件状态。 然后,组件根据状态呈现预加载器(如果需要)。
alert
和confirm
应该不成问题。 他们阻止和警报甚至不接受来自用户的任何input。 confirm
,如果用户select会影响组件渲染,则可以根据用户点击的内容设置状态。 如果不是,则可以将select存储为组件成员variables以供以后使用。
我是唯一一个认为加载指标不属于Redux商店的人吗? 我的意思是,我不认为这是应用程序本身的一部分。
现在,我与Angular2合作,我所做的是我有一个“加载”服务,通过RxJS BehaviourSubjects公开不同的加载指标。我想机制是相同的,我只是不存储在Redux中的信息。
LoadingService的用户只需订阅他们想要收听的事件。
每当需要更改时,我的Redux动作创build者都会调用LoadingService。 UX组件订阅暴露的observables …
我们的应用程序中有三种types的通知,所有这些通知均被devise为以下几个方面:
- 加载指标(基于道具的模态或非模态)
- popup错误(模态)
- 通知零食(非模态,自闭)
所有这三个都在我们的应用程序(主)的顶层,并通过Redux连线,如下面的代码片段所示。 这些道具控制着他们相应方面的显示。
我devise了一个代理来处理我们所有的API调用,因此所有的捕获和(api)错误都是由我在代理中导入的actionCreators调解的。 (另外,我还使用webpack为dev开辟了一个支持服务的模拟,所以我们可以在没有服务器依赖的情况下工作)。
需要提供任何types的通知的应用程序中的任何其他地方只是导入适当的操作。 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) }}
)导出默认类Main扩展React.Component {
我保存的url如::
isFetching: { /api/posts/1: true, api/posts/3: false, api/search?q=322: true, }
然后我有一个记忆select器(通过重新select)。
const getIsFetching = createSelector( state => state.isFetching, items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false );
为了使POST的情况下唯一的URL,我传递一些variables作为查询。
而我想要显示一个指标的地方,我只需使用getFetchCountvariables