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,您的组件将忽略结果。
顺便说一句:componentDidMount
和 componentWillReceiveProps
根据定义只能按该顺序运行。我怀疑您的第一个 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