带有 setInterval 的增量反应游戏:在现有状态转换期间无法更新



我试图让这段代码作为一个"增量"游戏工作,选项将在一些积分后显示,但我收到以下警告(只有一次(:

警告:无法在未挂载的组件上调用 setState(或 forceUpdate(。

index.js:2178 警告:在现有状态下无法更新 过渡(例如在render内或其他组件的 构造函数(。渲染方法应该是道具的纯函数和 州;构造函数副作用是一种反模式,但可以移动 到componentWillMount.

这是代码:

import React, { Component } from 'react';
import { Grid, Button } from "semantic-ui-react";
class EventDashboard extends Component {
constructor(props) {
super(props)
this.state={
credits: 0,
newOption: false,
newOptionDirty: false,
}
this.addCredits = this.addCredits.bind(this)
this.renderNewOption = this.renderNewOption.bind(this)
this.intervalID = null;
}
addCredits() {
this.setState((previousState) => ({
credits: previousState.credits + 1
}))
}
componentDidMount() {
this.intervalID = setInterval(() => {
this.addCredits()
}, 1000)
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
renderNewOption() {
if(this.state.credits === 5 && !this.state.newOptionDirty) {
// the warning happens here
this.setState(() =>({
newOptionDirty: true
}))
}
if(this.state.newOptionDirty) {
return(
<div>
<Button> Add new feature </Button>
</div>
)
} else {
return (
<div>no options until 5 credits</div>
)
}
}
render() {
return (
<Grid>
<Grid.Column width={10}>
<Button
onClick={this.addCredits}
>AddCredits</Button>
{this.renderNewOption()}
</Grid.Column>
<Grid.Column width={6}>
<h1>Credits</h1>
<h2>{this.state.credits}</h2>
</Grid.Column>
</Grid>
)
}
}
export default EventDashboard

尽管有此警告,但一切正常。

我错过了什么好做法?

问题确实出在您的renderNewOption()方法以及如何使用它上。

您正在从render()调用renderNewOption(),这很糟糕,因为您正在其中执行setState()。请记住,无论何时执行setState(),您都会更新状态并触发新的渲染。因此,在render()内部setState()将创建一个无限循环,因为渲染函数会不断调用自身。

在这种特殊情况下,您实际上不会得到无限循环,因为您有一个if-case 可以防止这种情况,但是您的组件将在第一次运行它(当newOptionDirty仍然false时(。发生这种情况时,您的组件尚未挂载,因为在调用此特定setState()之前,render()从未完成。

TL;DR: 切勿在渲染过程中调用setState()


试试这个:

componentDidMount() {
setInterval(() => {
this.addCredits()
}, 1000);
}
addCredits() {
this.setState((previousState) => ({
credits: previousState.credits + 1
});
}
renderNewOption() {
if(this.state.credits >= 5) {
return(
<div>
<Button> Add new feature </Button>
</div>
)
} else {
return (
<div>no options until 5 credits</div>
)
}
}

我们不需要(也不应该(在渲染中创建一些新的状态变量。我们感兴趣的只是credits是否等于5

根据其值,我们返回相应的 React 元素(按钮或无按钮(。

每当你在组件中做任何异步的事情,如回调/承诺/setTimeout,你可能会遇到组件可能已经被卸载的情况,你将尝试更新卸载的组件(就像在你的例子中一样(,这可能会导致内存泄漏。这是外部状态管理库和中间件(如 redux 和 redux-saga/redux-observable(的用例之一。

但是,如果要在组件中执行此操作,则需要在组件卸载时注意进行必要的清理。这是 forcomponentWillUnmount的用途:

constructor(props) {
super(props)
...
this.intervalID = null;
}
componentDidMount() {
this.intervalID = setInterval(() => {
this.addCredits()
}, 1000)
}
componentWillUnmount() {
clearInterval(this.intervalID);
}

最新更新