setState(…):只能更新已安装或正在安装的组件。这通常意味着您在未安装的组件上调用了setState()。这是无人值守


77
componentDidMount(prevProps, prevState, prevContext) {
    let [audioNode, songLen] = [this.refs.audio, List.length-1];

    audioNode.addEventListener('ended', () => {
        this._endedPlay(songLen, () => {
            this._currSong(this.state.songIndex);
            this._Play(audioNode);
        });
    });

    audioNode.addEventListener('timeupdate', () => {
        let [remainTime, remainTimeMin, remainTimeSec, remainTimeInfo] = [];

        if(!isNaN(audioNode.duration)) {
            remainTime = audioNode.duration - audioNode.currentTime;
            remainTimeMin = parseInt(remainTime/60);  // 剩余分
            remainTimeSec = parseInt(remainTime%60);  // 剩余秒

            if(remainTimeSec < 10) {
                remainTimeSec = '0'+remainTimeSec;
            }
            remainTimeInfo = remainTimeMin + ':' + remainTimeSec;
            this.setState({'time': remainTimeInfo});
        }
    });
}

componentWillUnmount () {
    let audio = this.refs.audio;
    audio.removeEventListener('timeupdate');
    audio.removeEventListener('ended');
}

错误:

警告:setState(...):只能更新已安装或正在安装的组件。这通常意味着您在未安装的组件上调用了setState()。这是无人值守。请检查未定义组件的代码。

我在中删除了“事件监听器” componentWillUnmount,但没有用。因为我添加this.setState({'time': remainTimeInfo});componentDidMount


Answers:


98

我通过为组件分配ref来解决此问题,然后在设置状态之前检查ref是否存在:

myMethod(){
  if (this.refs.myRef) 
   this.setState({myVar: true});
}

render() {
  return (
    <div ref="myRef">
      {this.state.myVar}
    </div>
  );
}

非常优雅的解决方案!
天鼓

11
谢谢你!但是,这非常严重。我几乎所有场景都基于基于承诺的获取进行加载,并引发此错误。为什么React不能只默默地拒绝在未安装状态下运行setState。并不是任何人都会看到差异。
mienaikoe

很棒的解决方案!!如此简单,如此有效!
污秽

这也不建议使用。但是,这是一个很好的解决方案!
Pramesh Bajracharya

为什么不反应,允许设置状态,因为这无关紧要。
PatS '18年

23

removeEventListener与的签名相同addEventListener。所有参数必须完全相同才能删除侦听器。

var onEnded = () => {};
audioNode.addEventListener('ended', onEnded, false);

this.cleanup = () => {
  audioNode.removeEventListener('ended', onEnded, false);
}

并在componentWillUnmount中调用this.cleanup()


4

我遇到此问题是因为我在构造函数中使用setState而不是state

更改以下错误代码

constructor(props) {
  super(props);
  this.setState({
    key: ''
  });
}

constructor(props) {
  super(props);
  this.state = {
    key: ''
  }; 
}

该死的,我实际上是偶然地这样做的。谢谢
user1189352

3

编辑isMounted已弃用,可能会在更高版本的React中删除。看到isMounted是一个Antipattern


作为警告状态,您呼叫this.setState的是一个组件上进行安装,但自那时以来已卸载。

为了确保您的代码安全,您可以将其包装

if (this.isMounted()) {
    this.setState({'time': remainTimeInfo});
}

以确保仍安装该组件。


4
this.isMounted已被弃用,并且不能解决事件监听器泄漏的问题(这主要是它被弃用的原因)
Brigand

感谢您指出这一点,但是为什么仍将其用于即通过AJAX加载初始数据?在哪里可以看到它的当前状态?
Daniel B


@Kolby,是的,我的回答提到了这一点。
Daniel B

3

自从更新了最新的React版本以来,我遇到了同样的问题。解决如下。

我的代码是

async componentDidMount() {
  const { default: Component } = await importComponent();
  Nprogress.done();
  this.setState({
    component: <Component {...this.props} />
  });
}

变成

componentWillUnmount() {
  this.mounted = false;
}
async componentDidMount() {
  this.mounted = true;
  const { default: Component } = await importComponent();
  if (this.mounted) {
    this.setState({
      component: <Component {...this.props} />
    });
  }
}

2

我以前有这个问题,并根据React的官方页面isMounted是Antipattern解决

将属性isMounted标志设置为true componentDidMount,并将其设置为false componentWillUnmount。当您setState()在回调中时,请先检查isMounted!这个对我有用。

state = {
    isMounted: false
  }
  componentDidMount() {
      this.setState({isMounted: true})
  }
  componentWillUnmount(){
      this.setState({isMounted: false})
  }

打回来:

if (this.state.isMounted) {
 this.setState({'time': remainTimeInfo});}

10
这不好。没有理由保存isMounted到状态。您应该简单地使用this.isMounted。这与反应状态无关。
苏珊(Sulthan)

1
此外,您不能在“ componentWillUnmount”上调用this.setState:learn.co/lessons/react-component-mount-and-unmounting
MarcoGodínez17年

1
根据调用的官方React文档,甚至this._isMounted可能不会与已弃用产生冲突或发出警告isMounted()
Cezar D.

实际上,应在componentWillUnmount中取消待处理的代码。这仍然是相同的反模式
ThaJay

1

这个线程中的许多答案都可以使用refs,但我认为一个完整的示例会很好。由于您通过使用事件侦听器并退出React上下文在实际的DOM节点上进行操作,因此应将ref视为标准解决方案。这是一个完整的示例:

class someComponent extends Component {
  constructor(props) {
    super(props)
    this.node = null
  }
  render() {
    return (
      <div ref={node => { this.node = node }}>Content</div>
    )
  }
  handleEvent(event) {
    if (this.node) {
      this.setState({...})
    }
  }
  componentDidMount() {
    //as soon as render completes, the node will be registered.
    const handleEvent = this.handleEvent.bind(this)
    this.node.addEventListener('click', handleEvent)
  }
  componentWillUnmount() {
    const handleEvent = this.handleEvent.bind(this)
    this.node.removeEventListener('click', handleEvent)
  }
}

1

addEventListener和removeEventListener,回调不得为Anonymous内部类,并且它们应具有相同的参数


2
正如@FakeRainBrigand所说,由于我没有评论的声誉,我想说的是“回调一定不能是匿名内部类”
Mingo

感谢您指出回调不应该是一个匿名函数
Wasim Abuzaher,

0

我想在Ajax请求的成功/失败回调中显示一个弹出窗口(引导模式)时,收到此警告。另外,setState无法正常工作,并且没有显示我的弹出模式。

以下是我的情况-

<Component /> (Containing my Ajax function)
    <ChildComponent />
        <GrandChildComponent /> (Containing my PopupModal, onSuccess callback)

我正在从孙子组件调用组件的ajax函数,并传递了onSuccess回调(在孙子组件中定义),该回调将状态设置为显示弹出模式。

我将其更改为-

<Component /> (Containing my Ajax function, PopupModal)
    <ChildComponent />
        <GrandChildComponent /> 

相反,我调用setState(onSuccess回调)在组件(ajax回调)本身中显示弹出模式,并解决了问题。

在第二种情况下:组件被渲染两次(我在HTML中两次包含bundle.js)。


0

audioNode.addEventListener的回调中使用具名的方法代替匿名函数应消除主题警告:

    componentDidMount(prevProps, prevState, prevContext) {
    let [audioNode, songLen] = [this.refs.audio, List.length-1];

    audioNode.addEventListener('ended', () => {
        this._endedPlay(songLen, () => {
            this._currSong(this.state.songIndex);
            this._Play(audioNode);
        });
    });

    audioNode.addEventListener('timeupdate', this.callbackMethod );
}

callBackMethod = () => {
    let [remainTime, remainTimeMin, remainTimeSec, remainTimeInfo] = [];

    if(!isNaN(audioNode.duration)) {
        remainTime = audioNode.duration - audioNode.currentTime;
        remainTimeMin = parseInt(remainTime/60);  // 剩余分
        remainTimeSec = parseInt(remainTime%60);  // 剩余秒

        if(remainTimeSec < 10) {
            remainTimeSec = '0'+remainTimeSec;
        }
        remainTimeInfo = remainTimeMin + ':' + remainTimeSec;
        this.setState({'time': remainTimeInfo});
    }
}

是的,无论如何都需要命名方法,因为它removeEventListener不能与匿名回调一起使用,如上所述。


0
  1. 取消中的所有异步操作 componentWillUnmount
  2. 由于不赞成使用isMounted标志setState,因此在异步调用时检查组件已经卸载。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.