javascript 为什么 componentDidUpdate() 会创建一个无限循环?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/46195559/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why does componentDidUpdate() create an infinite loop?
提问by noobie
I've stored urland a tokenin statein Parentcomponent. I'm passing an urland a tokenas propsfrom parent Componentto child Component. However, if there is some event in parent Component, setState()is triggered and as a result, componentDidUpdate()of child Componentgets executed.
As componentDidUpdate()was causing an infinite loop (as it triggers setState() inside child component), I've placed condition. But this does not prevent the error.
Child Component ie DisplayRevenueis as follows:
我已经存储url和token在state中Parent的组成部分。我正在将 anurl和 a tokenasprops从 parentComponent传递给 child Component。但是,如果 parent 中存在某些事件Component,setState()则会触发并因此执行componentDidUpdate()child Component。
由于componentDidUpdate()导致无限循环(因为它在子组件内触发 setState() ),我已经放置了条件。但这并不能防止错误。
子组件ieDisplayRevenue如下:
import React, { Component } from 'react';
import '../App.css';
import ListData from './listdata.js'
var axios = require('axios');
class DisplayRevenue extends Component {
constructor(props){
super(props);
this.state = { data:[], url:"" }
console.log(this.props.url);
}
componentWillMount() {
this.loadRevenue(this.props.url, this.props.token);
}
componentDidUpdate(){ //creates infinite loop
// console.log(this.props.url);
this.loadRevenue(this.props.url, this.props.token);
}
setData(data){
//if(this.state.url != this.props.url){
if(this.state.data != data.data){
console.log(data.data); //(1)
// console.log(this.state.url); //(2)
this.setState(data:data);
console.log(this.state.data); //(3)
// console.log(this.props.url); //(4)
} //(1) & (3) yields exactly same value so does (2) & (4)
}
loadRevenue(url,token){
axios({
method:'get',
url:url,
headers: {
Authorization: `Bearer ${token}`,
},
})
.then( (response) => {
// console.log(response.data);
this.setData(response.data);
})
.catch(function (error) {
console.log("Error in loading Revenue "+error);
});
}
render() {
return (
<ListData data={this.state.data}/>
);
}
};
export default DisplayRevenue;
Parent Component ie MonthToDate is as below:
父组件即 MonthToDate 如下:
import React, { Component } from 'react';
import '../App.css';
import DisplayRevenue from './displayRevenue'
var axios = require('axios');
class MonthToDate extends Component {
constructor(props){
super(props);
this.state = {
data:null,
url:"http://localhost:3000/api/monthtodate"
}
//console.log(this.props.location.state.token);
}
groupBySelector(event){
if ((event.target.value)==="invoice"){
this.setState({url:"http://localhost:3000/api/monthtodate"})
} else if ((event.target.value)==="customer") {
this.setState({url:"http://localhost:3000/api/monthtodate?group-by=customerNumber"})
} else if ((event.target.value)==="month") {
this.setState({url:"http://localhost:3000/api/invoices?group-by=month"})
} else {
this.setState({url:"http://localhost:3000/api/monthtodate"})
}
console.log(this.state.url);
}
render() {
return (
<div>
<select onChange={(event)=>this.groupBySelector(event)}>
<option value="invoice">GROUP BY INVOICE</option>
<option value="customer">GROUP BY CUSTOMER</option>
<option value="month">GROUP BY MONTH</option>
</select>
<DisplayRevenue url={this.state.url} token={this.props.location.state.token}/>
</div>
);
}
}
export default MonthToDate;
- What am I missing?
- Also, after I've received the
urlin the child component I want to render different component based on thaturl. For example<ListData />component can handle only one type ofurl. How can I render another component withinrender()based on theurltype??
- 我错过了什么?
- 此外,在我收到
url子组件中的 之后,我想基于该 呈现不同的组件url。例如<ListData />组件只能处理一种类型的url. 如何render()根据url类型渲染另一个组件?
回答by Sagiv b.g
You are calling an ajax call in componentDidUpdate, and you set the state on the callback, that will trigger another call and update which will call the ajax request again and callback will set state again and so on.
Your condition in setData:
您正在调用 ajax 调用componentDidUpdate,并在回调中设置状态,这将触发另一个调用和更新,该调用和更新将再次调用 ajax 请求,回调将再次设置状态,依此类推。
您的情况setData:
if(this.state.data != data.data)
will always return true as objects are reference type and can't be compared, no matter what data returned from the ajax call it will always be a different object and will return truein your condition.
Example:
将始终返回 true,因为对象是引用类型且无法进行比较,无论从 ajax 调用返回什么数据,它都将始终是不同的对象,并将true在您的条件下返回。例子:
var obj1 = {a:1}
var obj2 = {a:1}
console.log(obj1 != obj2); // returns true
What you can do, is compare primitivesvalues inside the two objects.
For example:
您可以做的是比较两个对象内的基元值。
例如:
if(this.state.data.id != data.id) // id could be a string or a number for example
EDIT
Another thing i forgot to mention which may not relate to your problem directly but should be enforced, Neverdo ajax requests inside componentWillMountor the constructorfor that matter, as the render function will be invoked before your ajax request will finish. you can read about it in the DOCS.
Ajax requests should be invoked in componentDidMountlife cycle methodinstead.
编辑
我忘了提到的另一件事可能与您的问题没有直接关系,但应该强制执行,永远不要在内部执行 ajax 请求componentWillMount或constructor就此而言,因为渲染函数将在您的 ajax 请求完成之前被调用。你可以在DOCS 中阅读它。
Ajax 请求应该在componentDidMount生命周期方法中调用。
EDIT #2
Another thing that can be helpful, in the MonthToDaterender function you are passing a new instance of a function on each render (which may cause a performance hit)
编辑 #2
另一件有用的事情,在MonthToDate渲染函数中,您在每个渲染上传递一个函数的新实例(这可能会导致性能下降)
<select onChange={(event)=>this.groupBySelector(event)}>
Try changing it to this (the event will be passed automatically to the handler):
尝试将其更改为此(事件将自动传递给处理程序):
<select onChange={this.groupBySelector}>
You would also need to bind it in the constructor:
您还需要在构造函数中绑定它:
constructor(props){
super(props);
this.state = {
data:null,
url:"http://localhost:3000/api/monthtodate"
}
//console.log(this.props.location.state.token);
this.groupBySelector = this.groupBySelector.bind(this); // binds this to the class
}

