反应无限循环 - 渲染调用 setState() 中的 onClick
React infinite loop - onClick inside a render calls setState()
React 的新手。我在渲染按钮组件时遇到了一些问题。我想要做的是创建一个按钮,当单击该按钮时,它会获取一些数据并将其作为列表显示在按钮本身下方。为此,我正在尝试进行条件渲染。
我使用按钮组件的状态作为获取的数据数,初始化为零。所以第一次我只会渲染按钮而不尝试渲染列表。单击按钮时,onClick 事件执行提取,获取数据。在这一点上,状态应该被更新,但是如果我调用 setState() 来更新它,React 当然会警告我我正在创建一个无限循环(我在 render() 中调用 setState()毕竟功能)。
最常见的生命周期组件对我没有帮助,因为当组件被安装时用户还没有按下按钮(那时不能使用 componentDidMount()),如果我从onClick 函数组件不会更新,所以我没有方法可以从中调用 setState() 。考虑到一个组件自己改变它的 props 是反模式的,我没主意了。
代码如下:
import { MapList } from './MapList';
export class ButtonFetcher extends React.Component
{
constructor(props)
{
super(props);
this.state = { numberOfMaps: 0 };
this.mapArray = [];
this.fetchHaloMaps = this.fetchHaloMaps.bind(this);
}
async fetchHaloMaps()
{
const url = 'https://cryptum.halodotapi.com/games/hmcc/metadata/maps'
fetch(url, {
"method": 'GET',
"headers": {
'Content-Type': 'application/json',
'Cryptum-API-Version': '2.3-alpha',
'Authorization': 'Cryptum-Token XXX'
}
})
.then(response =>
response.json())
.then(res => {
let d;
let i=0;
for (; i< res.data.length; i++)
{
d = res.data[i];
this.mapArray[i] = d.name;
}
this.setState(({
numberOfMaps : i
}));
})
.catch(error => {
console.log("There was an error: " + error);
});
}
render()
{
if (this.state.numberOfMaps === 0)
{
return (
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
)
}
else
{
return (
<div>
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
<MapList mapNames={this.mapArray} />
</div>
)
}
}
}
堆栈片段:
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<script type="text/babel" data-presets="es2017,react,stage-3">
const { useState } = React;
// Promise-based delay function
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// Stand-in for `MapList`
const MapList = ({mapNames}) => <ul>
{mapNames.map(name => <li key={name}>{name}</li>)}
</ul>;
/*export*/ class ButtonFetcher extends React.Component
{
constructor(props)
{
super(props);
this.state = { numberOfMaps: 0 };
this.mapArray = [];
this.fetchHaloMaps = this.fetchHaloMaps.bind(this);
}
async fetchHaloMaps()
{
const url = 'https://cryptum.halodotapi.com/games/hmcc/metadata/maps'
/*
fetch(url, {
"method": 'GET',
"headers": {
'Content-Type': 'application/json',
'Cryptum-API-Version': '2.3-alpha',
'Authorization': 'Cryptum-Token XXX'
}
})
.then(response =>
response.json())
*/
delay(800) // ***
.then(() => ({ // ***
data: [ // ***
{name: "one"}, // *** A stand-in for the fetch
{name: "two"}, // ***
{name: "three"}, // ***
] // ***
})) // ***
.then(res => {
let d;
let i=0;
for (; i< res.data.length; i++)
{
d = res.data[i];
this.mapArray[i] = d.name;
}
this.setState(({
numberOfMaps : i
}));
})
.catch(error => {
console.log("There was an error: " + error);
});
}
render()
{
if (this.state.numberOfMaps === 0)
{
return (
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
);
}
else
{
return (
<div>
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
<MapList mapNames={this.mapArray} />
</div>
);
}
}
}
ReactDOM.render(<ButtonFetcher />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/regenerator-runtime@0.13.2/runtime.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.10.3/babel.min.js"></script>
我通过编辑我没有透露的和组件解决了这个问题。剧透:只是一些从 props 到 state 的错误分配,以及在这两个组件的渲染中一些“语法”错误 returns。我被 VisualStudio Code 抛出的警告所欺骗,就是这个警告(我必须说,在我修复代码后它仍然存在,然后呈现列表):
Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.
at ButtonFetcher (http://localhost:3000/static/js/bundle.js:108:5)
at div
at App
调试项目时,我在 MapList.js 和 MapEntry.js 文件的开头设置了一些断点,这些断点在代码执行期间从未到达:这让我误以为我在渲染时犯了一个错误按钮。我所说的“将 setState() 调用到 render()”的意思是我将异步函数 fetchHaloMaps()(在它的末尾调用了 setState())关联为 render( ).但是,在解决问题之后,解释似乎很明显:渲染按钮并且在渲染过程中没有调用该函数,而是在按钮已经渲染之后由用户调用 - 根本没有渲染的无限循环,我发布的那段内容符合我的预期,我的问题现在看起来很愚蠢 :)
顺便说一下,我仍然无法弄清楚为什么会出现该警告以及它对应用程序有何影响。
React 的新手。我在渲染按钮组件时遇到了一些问题。我想要做的是创建一个按钮,当单击该按钮时,它会获取一些数据并将其作为列表显示在按钮本身下方。为此,我正在尝试进行条件渲染。 我使用按钮组件的状态作为获取的数据数,初始化为零。所以第一次我只会渲染按钮而不尝试渲染列表。单击按钮时,onClick 事件执行提取,获取数据。在这一点上,状态应该被更新,但是如果我调用 setState() 来更新它,React 当然会警告我我正在创建一个无限循环(我在 render() 中调用 setState()毕竟功能)。
最常见的生命周期组件对我没有帮助,因为当组件被安装时用户还没有按下按钮(那时不能使用 componentDidMount()),如果我从onClick 函数组件不会更新,所以我没有方法可以从中调用 setState() 。考虑到一个组件自己改变它的 props 是反模式的,我没主意了。
代码如下:
import { MapList } from './MapList';
export class ButtonFetcher extends React.Component
{
constructor(props)
{
super(props);
this.state = { numberOfMaps: 0 };
this.mapArray = [];
this.fetchHaloMaps = this.fetchHaloMaps.bind(this);
}
async fetchHaloMaps()
{
const url = 'https://cryptum.halodotapi.com/games/hmcc/metadata/maps'
fetch(url, {
"method": 'GET',
"headers": {
'Content-Type': 'application/json',
'Cryptum-API-Version': '2.3-alpha',
'Authorization': 'Cryptum-Token XXX'
}
})
.then(response =>
response.json())
.then(res => {
let d;
let i=0;
for (; i< res.data.length; i++)
{
d = res.data[i];
this.mapArray[i] = d.name;
}
this.setState(({
numberOfMaps : i
}));
})
.catch(error => {
console.log("There was an error: " + error);
});
}
render()
{
if (this.state.numberOfMaps === 0)
{
return (
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
)
}
else
{
return (
<div>
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
<MapList mapNames={this.mapArray} />
</div>
)
}
}
}
堆栈片段:
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<script type="text/babel" data-presets="es2017,react,stage-3">
const { useState } = React;
// Promise-based delay function
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// Stand-in for `MapList`
const MapList = ({mapNames}) => <ul>
{mapNames.map(name => <li key={name}>{name}</li>)}
</ul>;
/*export*/ class ButtonFetcher extends React.Component
{
constructor(props)
{
super(props);
this.state = { numberOfMaps: 0 };
this.mapArray = [];
this.fetchHaloMaps = this.fetchHaloMaps.bind(this);
}
async fetchHaloMaps()
{
const url = 'https://cryptum.halodotapi.com/games/hmcc/metadata/maps'
/*
fetch(url, {
"method": 'GET',
"headers": {
'Content-Type': 'application/json',
'Cryptum-API-Version': '2.3-alpha',
'Authorization': 'Cryptum-Token XXX'
}
})
.then(response =>
response.json())
*/
delay(800) // ***
.then(() => ({ // ***
data: [ // ***
{name: "one"}, // *** A stand-in for the fetch
{name: "two"}, // ***
{name: "three"}, // ***
] // ***
})) // ***
.then(res => {
let d;
let i=0;
for (; i< res.data.length; i++)
{
d = res.data[i];
this.mapArray[i] = d.name;
}
this.setState(({
numberOfMaps : i
}));
})
.catch(error => {
console.log("There was an error: " + error);
});
}
render()
{
if (this.state.numberOfMaps === 0)
{
return (
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
);
}
else
{
return (
<div>
<button type="button" onClick={this.fetchHaloMaps} >Retrieve giafra's last maps!</button>
<MapList mapNames={this.mapArray} />
</div>
);
}
}
}
ReactDOM.render(<ButtonFetcher />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/regenerator-runtime@0.13.2/runtime.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.10.3/babel.min.js"></script>
我通过编辑我没有透露的
Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering. at ButtonFetcher (http://localhost:3000/static/js/bundle.js:108:5) at div at App
调试项目时,我在 MapList.js 和 MapEntry.js 文件的开头设置了一些断点,这些断点在代码执行期间从未到达:这让我误以为我在渲染时犯了一个错误按钮。我所说的“将 setState() 调用到 render()”的意思是我将异步函数 fetchHaloMaps()(在它的末尾调用了 setState())关联为 render( ).但是,在解决问题之后,解释似乎很明显:渲染按钮并且在渲染过程中没有调用该函数,而是在按钮已经渲染之后由用户调用 - 根本没有渲染的无限循环,我发布的那段内容符合我的预期,我的问题现在看起来很愚蠢 :)
顺便说一下,我仍然无法弄清楚为什么会出现该警告以及它对应用程序有何影响。