Redux:从Component请求成功或错误流程(使用redux-saga)

时间:2017-06-27 20:21:07

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

这是我还没有找到标准解决方案的一件事。

我将我的商店设置为redux-saga用于副作用处理,我从一个组件调度一个动作(具有异步副作用),现在希望组件在处理副作用后执行某些操作(例如导航到另一条路线/屏幕,弹出模态/烤面包或其他任何东西)。

此外,我想显示加载指示符或失败时的任何错误。

在redux之前,这种流程是直截了当的,它看起来像这样:

try {
  this.setState({loading: true});
  const result = await doSomeRequest();
  this.setState({item: result, loading: false});
} catch (e) {
  this.setState({loading: false, error: e});
}

使用redux,我通常希望调度启动流程的操作并在商店中包含所有相关信息,以允许许多组件收听正在发生的事情。

我可以有3个动作,'请求','成功','失败'。 在我的组件中,我将发送请求的操作。 负责任的传奇将在处理“请求”时发送“成功”或“失败”行动。

我的组件将反映更改。 但我还没有找到一种标准的方法来确定行动是否已经完成。也许商店没有因为异步操作而更新(NO-OP,但加载状态仍然会改变我猜)。但是行动仍然成功,我想做一些事情,比如导航到另一个屏幕。

我真的尝试在redux docs,redux-saga docs或Stackoverflow / Google中找到这种(看似常见的)场景,但没有成功。

注意:同样使用redux-thunk我认为这种行为很容易实现,因为我可以直接执行异步操作调度并接收成功操作或catch中的错误操作(如果我是的话,请更正我错了,从来没有真正用过thunk)。但我还没有看到用redux-saga实现的相同行为。

我提出了3个具体的解决方案:

  1. 最原始的解决方案,只处理组件中的“成功”/“失败”操作。现在这个解决方案我不是一个忠实的粉丝。在我的具体实现中,没有任何操作表明异步请求已经启动。副作用在Component中处理,而不是在传奇中被抽象出来。很多潜在的代码重复。

  2. 在发送请求操作之前运行一次性传奇,相互竞争“成功”/“失败”操作,并允许对第一个发生的操作做出反应。为此,我写了一个帮助,将摘要的运行抽象化:https://github.com/milanju/redux-post-handling-example/blob/master/src/watchNext.js 这个例子我比1更喜欢它,因为它简单而且具有说服力。虽然我不知道在这样的运行时间创建一个传奇是否会产生任何负面影响,或者可能还有另一种“正确”的方法来实现我对redux-saga所做的事情?

  3. 将与操作相关的所有内容(loading,successFlag,error)放入存储中,并在componentWillReceiveProps中对操作更改做出反应((!this.props.success&& nextProps.success))表示操作已经成功完成)。 这与第二个示例类似,但适用于您选择的任何副作用处理解决方案。 也许我正在监督一些事情,比如检测到一个行动成功无法工作如果道具非常快,道具进入componentWillReceiveProps将“堆积”并且组件跳过从非成功到完全成功的过渡?

    < / LI>

    请随时查看我为此问题创建的示例项目,该项目已实施完整的示例解决方案:https://github.com/milanju/redux-post-handling-example

    我希望能够对我用于处理所描述的操作流程的方法进行一些输入。

    1. 我在这里误解了什么吗?我提出的“解决方案”根本不是直截了当的。也许我从错误的角度看问题。
    2. 以上示例是否有任何问题?
    3. 此问题是否有最佳做法或标准解决方案?
    4. 您如何处理描述的流程?
    5. 感谢阅读。

3 个答案:

答案 0 :(得分:3)

如果我正确理解了您的问题,您希望您的组件根据您的传奇所触发的操作采取措施。这通常发生在componentWillReceiveProps中 - 使用新道具调用该方法,而旧道具仍可通过this.props使用。

然后将状态(请求/成功/失败)与旧状态进行比较,并相应地处理各种转换。

如果我误解了某些内容,请告诉我。

答案 1 :(得分:3)

我通过以下方式在使用saga的组件中实现异步操作回调:

const myApp = () => {
  store.subscribe(render);

  render();
};

module.exports = myApp;

const render = () => {
  const rootDOMElement = document.getElementById('root');

  ReactDOM.render(

    <section className="myApp">
      ...
    </section>,
    rootDOMElement

  );
};

使用class CallbackableComponent extends Component { constructor() { super() this.state = { asyncActionId: null, } } onTriggerAction = event => { if (this.state.asyncActionId) return; // Only once at a time const asyncActionId = randomHash(); this.setState({ asyncActionId }) this.props.asyncActionWithId({ actionId: asyncActionId, ...whateverParams }) } static getDerivedStateFromProps(newProps, prevState) { if (prevState.asyncActionId) { const returnedQuery = newProps.queries.find(q => q.id === prevState.asyncActionId) return { asyncActionId: get(returnedQuery, 'status', '') === 'PENDING' ? returnedQuery.id : null } } return null; } } 缩减器:

queries

最后,我的import get from 'lodash.get' const PENDING = 'PENDING' const SUCCESS = 'SUCCESS' const FAIL = 'FAIL' export default (state = [], action) => { const id = get(action, 'config.actionId') if (/REQUEST_DATA_(POST|PUT|DELETE|PATCH)_(.*)/.test(action.type)) { return state.concat({ id, status: PENDING, }) } else if ( /(SUCCESS|FAIL)_DATA_(GET|POST|PUT|PATCH)_(.*)/.test(action.type) ) { return state .filter(s => s.status !== PENDING) // Delete the finished ones (SUCCESS/FAIL) in the next cycle .map( s => s.id === id ? { id, status: action.type.indexOf(SUCCESS) === 0 ? SUCCESS : FAIL, } : s ) } return state } 通过检查CallbackableComponent是否存在来了解查询是否已完成。

但这需要付出代价:

  1. 向商店添加条目(虽然这是不可避免的)
  2. 在组件上添加了很多复杂性。
  3. 我希望:

    1. 要在传奇方面保留的this.state.asyncActionId逻辑(例如,当使用asyncActionId执行异步操作connect时,它会在调用时返回mapActionsToProps:{ {1}},例如id
    2. 要由const asyncActionId = this.props.asyncAction(params)抽象的商店部分,就像setTimeout负责将当前路线添加到商店一样。
    3. 目前,我无法找到更清晰的方法来实现这一目标。但我希望能对此有所了解!

答案 2 :(得分:2)

也许我不明白你面临的问题,但我猜他们的意思是客户看起来像这样:

mapStateToProps = state => {
 return {
   loading: state.loading,
   error  : state.error,
   data   : state.data 
 };
}

,组件将呈现如下:

return(
   {this.props.loading  && <span>loading</span>}
   {!this.props.loading && <span>{this.props.data}</span>}
   {this.props.error    && <span>error!</span>}
)

当调度requested操作时,其reducer会将商店状态更新为{loading: true, error: null}

当调度succeeded操作时,其reducer会将商店状态更新为{loading: false, error: null, data: results}

当调度failed操作时,其reducer会将商店状态更新为{loading: false, error: theError}

这样您就不必使用componentWillReceiveProps了。 我希望很清楚。