componentWillReceiveProps 和 componentDidMount 之间的竞争条件

Posted

技术标签:

【中文标题】componentWillReceiveProps 和 componentDidMount 之间的竞争条件【英文标题】:Race condition between componentWillReceiveProps and 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 快速连续触发,则它是无操作的。或者您可以在存储负载上使用去抖动 【参考方案1】:

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

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

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

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

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

    Ajax 调用 1(在本示例中在 componentDidMount 中触发) Ajax 调用 2(在 componentWillReceiveProps 中触发,由组件的父级触发) 来自呼叫 2 的响应进来 来自呼叫 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 解决方案的链接。 我在答案中添加了一个通用的清理反应组件。超出了您最初的问题,但可能会有所帮助。

以上是关于componentWillReceiveProps 和 componentDidMount 之间的竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

为啥 React 保留 componentWillReceiveProps 和 shouldComponentUpdate 方法?

react componentWillReceiveProps

如何明确替换 componentWillReceiveProps 并不断获取 nextProps?

将项目中的componentWillReceiveProps切换到getDerivedStateFromProps

何时使用 componentWillReceiveProps 生命周期方法?

将对象作为道具传递是不是会干扰 componentWillReceiveProps?