【问题标题】:setTimeout not fully cancelled before component unmounts在组件卸载之前 setTimeout 未完全取消
【发布时间】:2018-08-07 11:32:41
【问题描述】:

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

为什么我仍然收到有关在未安装组件中设置状态的错误?在错误跟踪中,它指向foo() 中的setTimeout。我在执行我的 api 调用之前清除了我的异步计时器并添加了一个检查 - 我看不到这个警告来自哪里。

componentDidMount() {
    this.setState({alive: true});
    this.foo();
}

componentWillUnmount() {
    this.setState({alive: false, timer: 0});
}

foo() {

    if (!this.state.alive) return;
    fetch('/api/etc/', {method: 'GET', headers: {'Cache-Control': 'no-cache'}})
    .then(res => res.json())
    .then(json => {

    if (!json.length) return;

    this.setState((prevState) => ({
        timer: setTimeout(this.foo.bind(this), 500)
    });
    });
}

【问题讨论】:

  • 你永远不会在任何地方清除计时器。不要在componentWillUnmount 中调用setState,如果需要,只需调用clearTimeout
  • 这行得通,谢谢。无论我读到什么,都将计时器设置为 0。

标签: javascript reactjs


【解决方案1】:

* 使用 React Hooks 更新了答案和函数组件版本 *

正如 Evan Trimboli 在下面的 the comment 中指出的那样,没有必要将状态中的超时 ID 存储为 it doesn't impact rendering

因此将timeout ID 存储在类实例中,并使用它来清除componentWillUnmount 中的超时。

运行下面的代码来查看它的实际效果

类组件版本(之前,不包括v16.8.0)

    class TodoApp extends React.Component {
    	timeout = 0;
    
      hello = () => console.log("hello world!")
      
      componentDidMount() {
      	this.timeout = setTimeout(this.hello, 500);
      }
      
      componentWillUnmount() {
      	clearTimeout(this.timeout);
      }
      
      render() {
        return (
          <div>demo</div>
        )
      }
    }

    ReactDOM.render(<TodoApp />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

    <div id="app"></div>

带有 Hook 的函数组件版本(自 v16.8.0 起可能)

React.useEffect 让我们将设置和拆卸逻辑放在同一个块中。

const TodoApp = () => {
  const hello = () => console.log("hello world!")

  React.useEffect(() => {
    const timeout = setTimeout(hello, 500);
    return () => clearTimeout(timeout);
  });
  
  return (
    <div>demo</div>
  )
}

    ReactDOM.render(<TodoApp />, document.querySelector("#app"))
<script src="https://unpkg.com/react@16.8.4/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.8.4/umd/react-dom.production.min.js"></script>

    <div id="app"></div>

旧答案(使用 State 存储超时 ID)。

将超时ID存储在状态中,并使用它来清除componentWillUnmount中的超时。

class TodoApp extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
    	timeout: null
    }
    
    this.hello = this.hello.bind(this);
  }
  
  hello() { console.log("hello world!"); }
  
  componentDidMount() {
  	const timeout = setTimeout(this.hello, 500);
  	this.setState({timeout});
  }
  
  componentWillUnmount() {
  	clearTimeout(this.state.timeout);
  }
  
  render() {
    return (
      <div>demo</div>
    )
  }
}

ReactDOM.render(<TodoApp />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>

【讨论】:

  • 最好将计时器存储在类实例中,因为它不会影响渲染。
  • @EvanTrimboli 你是绝对正确的。我已根据您的建议更新了答案。谢谢埃文?
猜你喜欢
  • 1970-01-01
  • 2020-09-09
  • 2021-03-25
  • 2022-06-17
  • 2021-09-13
  • 2015-12-26
  • 2017-10-03
  • 2014-01-20
  • 1970-01-01
相关资源
最近更新 更多