使用setInterval的增量React游戏:在现有状态转换期间无法更新

时间:2018-05-19 08:27:38

标签: reactjs

我试图将此代码作为“增量”游戏运行,其中选项将在一些积分后显示,但我得到以下警告(仅一次):

警告:无法在已卸载的组件上调用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

尽管有这种警告,一切正常。

我错过了什么好习惯?

2 个答案:

答案 0 :(得分:1)

每当您在组件中执行任何异步操作(如callbacks / Promises / setTimeout)时,您可能会遇到组件可能已卸载的情况,并且您将尝试更新可能导致的已安装组件(如您的情况)内存泄漏。这是外部状态管理库和中间件(如redux和redux-saga / redux-observable)的用例之一。

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

的内容
constructor(props) {
  super(props)
  ...
  this.intervalID = null;
}

componentDidMount() {
  this.intervalID = setInterval(() => {
    this.addCredits()
  }, 1000)
}

componentWillUnmount() {
  clearInterval(this.intervalID);
}

答案 1 :(得分:1)

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

您正在从renderNewOption()调用render(),这是不好的,因为您正在执行setState()。请记住,只要执行setState(),就会更新状态并触发新的渲染。因此,在setState()内部render()将创建一个无限循环,因为渲染函数将继续调用自身。

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

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元素(按钮或无按钮)。