【问题标题】:Redux setState called after component unmountedRedux setState 在组件卸载后调用
【发布时间】:2018-01-02 01:11:59
【问题描述】:

我正在构建一个包含 3 个主要组件的 React/Redux 应用程序 - 我正在尝试循环使用的说明、StoryFeed 和测验 4 轮(3 + 1 次练习)。

我有一个时钟组件(嵌套在 StoryFeed 中 组件),并且它设置为在计时器时移动到测验组件 达到零。但是,它似乎在调用 setState 之后 卸载并给出无限错误

警告:setState(...): 只能更新挂载或挂载 零件。这通常意味着您在未安装的设备上调用了 setState() 零件。这是无操作的。

我不知道如何防止这种情况。下面是Clock 组件的代码:

import React from 'react'
import PropTypes from 'prop-types'

import ReactInterval from 'react-interval'

class Clock extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
      timed: props.timed,
      counting: true
    }

    this.tick = this.tick.bind(this)
  }

  reset() {
    this.setState({
      counting: true,
      count: this.state.count + 1
    })

  }
  componentWillUnmount() {
    this.setState({ counting: false }, () => this.props.complete())
  }
  tick() {
    const { count, timed, counting } = this.state
    if (count + 1 > timed && counting) {
      this.componentWillUnmount()
    } else {
      this.reset();
    }
  }

  render() {
    return (
      <div className="clock alert alert-warning">
        <ReactInterval
          timeout={1000}
          enabled={this.props.timed > 1 && this.state.count < this.props.timed}
          callback={() => this.tick()}
        />
        <span>{this.state.timed - this.state.count}</span>
      </div>
    )
  }
}

Clock.propTypes = {
  timed: PropTypes.number,
  complete: PropTypes.func
}

export default Clock

这是父组件StoryFeed 代码:

import React from 'react'
import Marquee from './Marquee'
import * as stories from '../stories'
import Clock from './Clock'
import { chunk, now } from '../utils'
import PropTypes from 'prop-types'

class StoryFeed extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      text: stories.example,
      currentTest: 1,
      count: 0,
      timed: props.timed,
      selected: []
    }
    this.storyLoad.bind(this)
    this.select = this.select.bind(this)
    this.isSelected = this.isSelected.bind(this)
  }

  componentDidMount() {
    document.body.classList.add('mosaic-full-screen')
    this.storyLoad();
  }

  componentWillUnmount() {
    document.body.classList.remove('mosaic-full-screen')


  }

  select(id) {
    if (this.state.selected.find(s => s.id == id)) return

    this.setState({
      selected: [...this.state.selected, { id, time: now() }]
    })
  }

  isSelected(id) {
    return this.state.selected.find(j => j.id === id)
  }

  storyLoad(state) {
    switch (this.state.currentTest){
      case 1:
      this.setState({text: stories.example});
        console.log(this.state.currentTest)
      break;
      case 2:
      this.setState({text: stories.colleagues});
      break;
      case 3:
      this.setState({text: stories.aroomforthenight});
      break;
      case 4:
      this.setState({text: stories.thepromotion});
      break;
    }
  };



  reset() {
    this.clock &&
      this.clock.reset(4, () => {
        this.setState({
          counting: true
        })
      })
  }


  render() {
    const { enterAnswers, id, show, timed } = this.props
    return (
      <div className="story">
        <div className='container'>
          <Marquee text={this.state.text.join(' - ')} loop={false} hoverToStop={true} />
        </div>
        <div className="controls">
          {timed && (
            <Clock
              timed={timed}
              complete={() => enterAnswers(id, this.state.selected, now())}

            />
          )}
        </div>
      </div>

      )
    }

}
StoryFeed.propTypes = {
  timed: PropTypes.number,

  enterAnswers: PropTypes.func,
  id: PropTypes.number,
  show: PropTypes.oneOf(['window', 'jigsaw'])
}
export default StoryFeed

这个问题的其他答案似乎是针对具体情况的

【问题讨论】:

  • 我发现你自己打电话给componentWillUnmount 很有趣,但是这是一个反应生命周期事件,应该在卸载组件之前由反应调用。你知道哪个组件发出警告吗?我想知道它是渲染StoryFeed 组件的组件还是真的是Clock 组件
  • @Icepickle 对componentWillUnmount 的调用背后的原因似乎是关闭此组件时正在修改主体本身(在 React 根节点之外)。完全合法地使用这种生命周期方法!
  • @ZekeDroid 当然,你可以调用它,但是,我认为不应该调用它,为什么不提供一个额外的方法,既可以从 tick 方法也可以从 @ZekeDroid 调用987654329@方法?您也不会在自己的答案中这样做,您只需设置一个标志,将其标记为未安装。所以是的,你可以这样称呼它,它是否提高了可读性或可维护性,不,我不这么认为,因为它总是会提出同样的问题
  • 哦,伙计,我对你所说的很不满意,因为我没有阅读整个代码 sn-p!不,你是完全正确的,它应该是它自己的方法。用户代码不应直接调用生命周期方法。

标签: reactjs redux


【解决方案1】:

您可以在卸载时设置方法属性,然后仅在未设置此属性时更新状态。例如:

componentWillUnmount() {
  this.unmounted = true;
}

...

someMethod() {
  if (!this.unmounted) this.setState{...}
}

【讨论】:

    【解决方案2】:

    解决此问题的一种方法如下

    class X extends Component {
      mounted = false 
      ss = (...args) => {
        this.mounted && this.setState(...args)
      }
      componentDidMount(){
        this.mounted = true 
      }
      componentWillUnMount(){ // or componentDidUnmount
        this.mounted = false 
      }
    }
    

    现在您可以使用this.ss 而不是this.setState,或者您可以在设置状态之前检查this.mounted

    我不确定这是最好的解决方案,但它确实解决了问题

    【讨论】:

    • 关于它是否是解决这个问题的最佳方案,我来回反复讨论,最后还是没问题。我的理由是,当您对组件执行异步操作时,您已经偏离了 React 世界,这是解决该问题的好方法。在尝试执行设置状态操作之前检查组件是否仍然挂载没有错!
    猜你喜欢
    • 2019-07-09
    • 1970-01-01
    • 1970-01-01
    • 2019-02-07
    • 2018-03-20
    • 1970-01-01
    • 1970-01-01
    • 2014-04-19
    • 2019-02-20
    相关资源
    最近更新 更多