【问题标题】:Does anyone have this issue? "To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method"有人有这个问题吗? “修复,取消componentWillUnmount方法中的所有订阅和异步任务”
【发布时间】:2018-12-01 20:23:39
【问题描述】:

我在实现 React 应用程序时遇到以下消息。有没有人有同样的问题?

警告:无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要解决此问题,请取消 componentWillUnmount 方法中的所有订阅和异步任务。在 ProductList 中(在 App.js:44)

我的入口页面是 ProductList 组件。加载入口页面后,如果我单击标题中的 LogOut,我会遇到该消息。有人对此有什么建议吗?

所以我提到了几个答案,比如

Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application

但是我无法解决它,为什么会发生这种情况。 :(

App.js

import React, { Component } from "react";
import { Route } from "react-router-dom";

import Header from "./Header";
import ProductList from "./ProductList";
import Login from "./Login";
import Logout from "./Logout";
import "./App.css";

class App extends Component {
  constructor() {
    super();
    this.state = {
      query: "",
      isLoggedIn: false
    };
    this.handleLoginStatus = this.handleLoginStatus.bind(this);
    this.handleLogoutStatus = this.handleLogoutStatus.bind(this);
    this.setSearchKeyword = this.setSearchKeyword.bind(this);
  }

  handleLoginStatus() {
    this.setState({ isLoggedIn: true });
  }

  handleLogoutStatus() {
    this.setState({ isLoggedIn: false });
  }

  setSearchKeyword(query) {
    this.setState({
      query: query
    });
  }

  render() {
    return (
      <div>
        <Header setSearchKeyword={this.setSearchKeyword} />
        <Route
          path="/"
          exact={true}
          render={() => (
            <ProductList
              query={this.state.query}
              isLoggedIn={this.state.isLoggedIn}
            />
          )}
        />
        <Route
          path="/login"
          render={() => (
            <Login
              isLoggedIn={this.state.isLoggedIn}
              handleLoginStatus={this.handleLoginStatus}
            />
          )}
        />
        <Route
          path="/logout"
          render={() => (
            <Logout
              isLoggedIn={this.state.isLoggedIn}
              handleLogoutStatus={this.handleLogoutStatus}
            />
          )}
        />
      </div>
    );
  }
}

export default App;

ProductList.js

import React, { PureComponent } from "react";
import { Table } from "react-bootstrap";

import axios from "axios";

class ProductList extends PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      products: null,
      loaded: false
    };
  }

  // componentWillReceiveProps(nextProps) {
  //   console.log(nextProps.query);
  // }

  componentDidUpdate() {
    const url =
      "https://localhost/product/search?query=" + this.props.query;

    const options = {
      method: "GET",
      headers: {
        Authorization: "Bearer " + localStorage.getItem("auth-token")
      },
      url
    };

    axios(options)
      .then(response => {
        let products = response.data;
        this.setState({ products: products });
      })
      .catch(error => {
        console.log("axios error", error);
      });
  }

  componentDidMount() {
    const url =
      "https://localhost/product/search?query=" + this.props.query;

    const options = {
      method: "GET",
      headers: {
        Authorization: "Bearer " + localStorage.getItem("auth-token")
      },
      url
    };

    axios(options)
      .then(response => {
        let products = response.data;
        this.setState({ products: products, loaded: true });
      })
      .catch(error => {
        console.log("axios error", error);
      });
  }

  // ComponentWillUnmount() {
  //   this.isUnmounted = true;
  // }

  render() {
    if (this.state.loaded) {
      let columnNames = ["Num", "Name", "Indications", "features"];
      let fieldNames = ["num", "name", "indications", "features"];

      var tableHeaders = (
        <tr>
          {columnNames.map(column => {
            return <th key={column}>{column}</th>;
          })}
        </tr>
      );

      var tableBody = this.state.products.map((product, i) => {
        return (
          <tr key={product + "_" + i}>
            {fieldNames.map((field, j) => {
              if (j === 0) {
                return <td key={product.name + "_" + i + "_" + j}>{i + 1}</td>;
              } else {
                return (
                  <td key={product.name + "_" + i + "_" + j}>{product[field]}</td>
                );
              }
            })}
          </tr>
        );
      });
    }
    return (
      <div>
        <Table striped bordered condensed hover>
          <thead>{tableHeaders}</thead>
          <tbody>{tableBody}</tbody>
        </Table>
      </div>
    );
  }
}

export default ProductList;

如果你能给点建议,那将是很大的帮助。

【问题讨论】:

标签: reactjs lifecycle


【解决方案1】:

问题出在componentDidUpdate 内部,因为this.setState 是异步操作,所以会出现错误。

这是怎么发生的?

  1. 发生注销操作
  2. ProductList componentDidUpdate 不受任何条件保护,this.setState 被无限调用。
  3. ProductList componentWillUnmount 触发器
  4. 来自componentDidUpdate 的异步this.setState({ products: products }) 操作尝试更新状态
  5. 出现错误

要解决您的问题,请在 componentDidUpdate 中添加一个条件。

官方文档about componentDidUpdate

您可以在 componentDidUpdate() 中立即调用 setState() 但请注意 它必须被包裹在上面例子中的条件中,或者 你会导致一个无限循环。也会造成额外的 重新渲染虽然对用户不可见,但可能会影响 组件性能。如果你试图将某个状态“镜像”到 来自上面的道具,请考虑直接使用道具。读 详细了解为什么将 props 复制到 state 会导致错误。

示例解决方案:

componentDidUpdate(prevProps) {
   // Please do not forget to compare props
   if (this.props.somethingChanged !== prevProps.somethingChanged) {
     // this.setState logic here
   }
}

【讨论】:

  • 非常感谢您的回答。我现在正确解决了这个问题,我不明白的一件事是为什么即使我单击注销(我退出 ProductList)也会发生第 2 步(ProductList componentDidUpdate 触发器)。如果可以的话,我想问一下:)
  • 嗨@AnnaLee 感谢您提出并指出这一点。实际上componentDidUpdate 永远不会被调用!我已经更新了我的答案。请如果我的回答帮助您将其标记为“已回答”,以便其他人可以看到它。祝你编码好运! :)
  • 谢谢@loelsonk,它有很大帮助。我会的。
猜你喜欢
  • 1970-01-01
  • 2019-10-26
  • 2021-03-24
  • 2019-10-20
  • 1970-01-01
  • 1970-01-01
  • 2012-04-05
  • 2021-01-28
  • 2021-06-23
相关资源
最近更新 更多