【问题标题】:Race condition between componentWillReceiveProps and componentDidMountcomponentWillReceiveProps 和 componentDidMount 之间的竞争条件
【发布时间】:2016-02-02 15:08:54
【问题描述】:

我有一个组件,它在 props 中获取一些数据并使用它们发出 ajax 请求。

var ItemList = React.createClass({
    propTypes: {
        filters: React.PropTypes.object.isRequired,
    },
    getInitialState: function() {
        return {items: []};
    },
    componentDidMount: function() {
        this.ajaxFetchItems(this.props.filters);
    },
    componentWillReceiveProps: function(nextProps) {
        this.ajaxFetchItems(nextProps.filters);
    },
    ajaxFetchItems: function(filter) {
        ....
        this.setState({items: data});
    }
}

问题是props几乎是立即更改的,有时componentDidMount中的ajax调用比componentWillReceiveProps中的稍慢,所以初始状态是在第一次更新后写入的。

如何避免慢的 componentDidMount 覆盖快的 componentWillReceiveProps?

有更好的方法来处理下载数据的 React 组件的生命周期吗?

【问题讨论】:

  • 仅在下一个 filters 属性与当前属性不同时获取
  • 我尽量避免在应用程序树中包含应用程序逻辑 - 所以在这种情况下,我会将过滤器应用于商店并告诉它重新加载,我的任何组件都会监​​听对商店的更改并在完成加载后更新。如果您愿意,您只能在 new_filters !== old_filters 时更新商店,这意味着如果 2 个 ajaxFetchItems 快速连续触发,则它是无操作的。或者您可以在存储负载上使用去抖动

标签: ajax reactjs


【解决方案1】:

您可以为处理的最新更新设置时间戳。 并以某种方式确保原始 Ajax 请求的时间戳包含在 Ajax 结果中。

并添加shouldComponentUpdate() 以检查接收到的结果是否具有比状态中的时间戳晚的时间戳。如果不是:返回 false,您的组件将忽略结果。

顺便说一句:componentDidMountcomponentWillReceiveProps 根据定义只能按该顺序运行。我怀疑您的第一个 Ajax 调用需要很长时间才能返回结果,而您的第二个调用很快。因此,您会以错误的顺序返回 Ajax 结果。 (不是因为反应慢)。

更新: 使用 shouldComponentUpdate 是处理这种情况的反应方式:其目的是允许将新状态与旧状态进行比较,并基于该比较,而不是重新渲染。

这个问题(很可能)是由 ajax 响应的进入顺序产生的:

  1. Ajax 调用 1(在本示例中在 componentDidMount 中触发)
  2. Ajax 调用 2(在 componentWillReceiveProps 中触发,由组件的父级触发)
  3. 来自呼叫 2 的响应进来
  4. 来自呼叫 1 的响应进来。

因此,一个更通用的问题/解决方案是“如何处理以错误顺序返回的 ajax 响应”。

时间戳(在 shouldComponentUpdate 中)是一种方法。

另一种方法(描述为here)是使第二个请求(在 componentWillReceiveProps 中)中止第一个 ajax 请求。

重温: 在进一步考虑之后(componentDidMount 和 componentWillReceiveProps 中的调用感觉不对),一种更通用的类似 react 的方式来处理您的组件可能如下:

您的组件的工作基本上是:

  • 通过道具接收过滤器,
  • 使用过滤器通过ajax获取列表,
  • 并呈现 ajax 响应 = 列表。

所以它有 2 个输入:

  • 过滤器(=道具)
  • 列表(= ajax 响应)

只有 1 个输出 = 列表(可能为空)。

工作:

  • 组件第一次接收filter作为prop:需要发出ajax请求,并渲染一个空列表或者一些加载状态。
  • 所有后续过滤器:组件应发出新的 ajax 请求(并终止可能未完成的旧请求),不应重新渲染 (!)
  • 当它收到一个 ajax 响应时,它应该重新呈现列表(通过更新状态)。

用 react 设置它可能看起来像这样:

getInitialState() {
  this.fetchAjax(this.props.filter);            // initiate first ajax call here
  return { list : [] };                         // used to maybe display "loading.." message
}
componentWillReceiveProps(nextProps) {
  this.fetchAjax(nextProps.filter);            // send off ajax call to get new list with new filter
}
shouldComponentUpdate(nextProps, nextState) {
  return (this.state.list != nextState.list);   // only update component if there is a new list
                                                // so when new props (filter) comes in there is NO rerender
}
render() {
  createChildrenWith(this.state.list);
}
fetchAjax(filter) {
  killOutStandingRequests();                    // some procedure to kill old ajax requests
  getListAsync…
    request: filter                             // request new list with new filter
    responseHandler: this.handleResponse        // add responseHandler
}
responseHandler(data) {
  this.setState({ list : data });               // put the new list in state, triggering render
}

状态中的原始时间戳可以解决上面发布的问题,但我想我会分享修改后的反应组件作为奖励......

【讨论】:

  • 是的,肯定是ajax调用时间太长,我不是很清楚。您的解决方案可以工作,但没有更惯用的方法吗?在我看来这是一个很常见的例子..
  • 我认为 shouldComponentUpdate (带时间戳)是解决此问题的反应方式。其他通用解决方案也是可能的,例如使用承诺,或者通过使第二个请求杀死第一个请求。我在更新的答案中提供了通用 javascript 解决方案的链接。
  • 我在答案中添加了一个通用的清理反应组件。超出了您最初的问题,但可能会有所帮助。
猜你喜欢
  • 2015-12-04
  • 2013-11-14
  • 2016-11-16
  • 1970-01-01
  • 2012-06-24
  • 2016-01-06
  • 1970-01-01
  • 2021-11-02
  • 2015-01-16
相关资源
最近更新 更多