我如何在 React 的卸载组件中设置状态

how can I setState in unmount Component in React

我们如何在参数中设置 componentDidMount 钩子中的状态?

我的问题是 React 通知的警告:

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.
in DiscoverPage (created by Route)

在我的 DiscoverPage 组件中:

import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import TvShows from './TvShows';
import Movies from './Movies';
import MovieDetail from "../MoviePage/MovieDetail";
import TvDetail from "../TvShowPage/TvDetail";

class DiscoverPage extends Component {

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

  getDataItem = (data) => {
    this.setState({ data: data });
  };

  render() {
    return (
      <Switch>
        <Route exact path="/discover/movie" render={props => <Movies data={this.getDataItem} {...props} />} />
        <Route path="/discover/tv" render={props => <TvShows data={this.getDataItem} {...props} />} />
        <Route path="/movie/:movie" render={props => <MovieDetail data={this.state.data} {...props} />} />
        <Route path="/tv/:tv" render={props => <TvDetail data={this.state.data} {...props} />} />
        <Redirect to="/discover/movie" />
      </Switch>
    );
  }
}

export default DiscoverPage;

在子组件中(本例为 Movies 组件):

import React, { Component } from 'react';
import requestApi from '../api';
import MovieList from "../MoviePage/MovieList";

class Movies extends Component {

  constructor(props) {
    super(props);
    this.state = {
      data: {
        page: 1,
        results: []
      }
    }
  }

  componentDidMount() {
    requestApi.fetchData('discover', 'movie').then(response => {
      this.setState({ data: response.data, isLoading: false });
    });
  }

  getMoviebyId = (id) => {
    requestApi.fetchDataById('movie', id).then(response => {
      this.props.data(response.data);
    });
  };

  nextPage = (e) => {
    const page = this.state.data.page + 1;
    requestApi.fetchDataPaginate('discover', 'movie', page).then(response => {
      this.setState({ data: response.data });
    });
  };
  prevPaginate = (e) => {
    const page = this.state.data.page - 1;
    requestApi.fetchDataPaginate('discover', 'movie', page).then(response => {
      this.setState({ data: response.data });
    });
  };

  render() {
    return (
      <div className="container">
        <div className="ss_media">
          <h2 className="title">Discover New Movies & TV Shows</h2>
          <MovieList
            movie={this.getMoviebyId}
            routeProps={this.props}
            prevPaginate={this.prevPaginate}
            nextPaginate={this.nextPage}
            moviesList={this.state.data} />
        </div>
      </div>
    );
  }
}

export default Movies;

我认为您缺少从 react-router-dom

导入 BrowserRouter
import { Switch, Route, Redirect, BrowserRouter } from 'react-router-dom'; // Add BrowserRouter

class DiscoverPage extends Component {

...
render() {
    return (
    <BrowserRouter> // Add this 
        <Switch>
            <Route exact path="/discover/movie" render={props => <Movies data={this.getDataItem} {...props} />} />
            <Route path="/discover/tv" render={props => <TvShows data={this.getDataItem} {...props} />} />
            <Route path="/movie/:movie" render={props => <MovieDetail data={this.state.data} {...props} />} />
            <Route path="/tv/:tv" render={props => <TvDetail data={this.state.data} {...props} />} />
            <Redirect to="/discover/movie" />
        </Switch>
    </BrowserRouter>
    );
}
}

我们可以在 componentDidMount 中设置状态,例如:

componentDidMount(){
    var {state, setState} = this.props;
    setState({doNotRender: true});
}

而是使用 componentDidUpdate 如:

componentDidUpdate(){
    var {state, setState} = this.props;
    if(!state.doNotRender){ //any_condition ur choice
       setState({doNotRender: true});
    }
}

您的 DiscoverPage 组件在路由更改时卸载,之后您调用 getDataItem 方法。这就是 React 抛出错误的原因。

我建议使用 react-router 组件方法,而不是渲染并将数据移动到存储,然后任何组件都可以访问该存储。参考 react-router render methods

示例:

<Route path="/user/:username" component={User}/>

需要做一些更改,您使用的逻辑效率不高
ass much as I understand from your question that would be good

import React, { Component } from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
import TvShows from './TvShows';
import Movies from './Movies';
import MovieDetail from '../MoviePage/MovieDetail';
import TvDetail from '../TvShowPage/TvDetail';

class DiscoverPage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            data: {}
        };
    }
    getALLDATA() {
        //update your state after your request
        request().then(res => {
            this.setState({
                data: res.data
            });
        });
    }
    componentDidMount() {
        this.getALLDATA();
    }
    getDataItem = () => {
        return this.state.data;
    };

    render() {
        return (
            <Switch>
                <Route exact path="/discover/movie" render={props => <Movies data={this.getDataItem} {...props} />} />
                <Route path="/discover/tv" render={props => <TvShows data={this.getDataItem} {...props} />} />
                <Route path="/movie/:movie" render={props => <MovieDetail data={this.state.data} {...props} />} />
                <Route path="/tv/:tv" render={props => <TvDetail data={this.state.data} {...props} />} />
                <Redirect to="/discover/movie" />
            </Switch>
        );
    }
}

export default DiscoverPage;

您收到的警告告诉您不能在未安装的组件(即当前未呈现的组件)上调用 setState。最有可能的是,您的其他组件之一(MoviesTvShows 等)正在调用您作为 data 道具传递的方法。当这些组件之一被渲染时,DiscoveryPage 组件不被渲染,所以通过 data 属性调用 getDataItem 将导致 DiscoveryPagesetState 方法在未呈现时被调用。要解决此问题(假设您想要做的是将数据从组件传递到组件),我建议可能使用 localStorage 来存储共享变量(或者,如@Anu 所建议的那样,使用其他类型的存储)。

我不明白你对 "in arguments" 的意思,但是,回答你的问题,你可以在任何 component... 方法中调用 setState(即使在 componentWillUnmount 几乎没有意义,因为只有在组件即将卸载时才会调用此方法)。

编辑: 查看 Movie 组件。在 getMovieById 方法中使用 localStorage 作为我们的商店,而不是:

getMoviebyId = (id) => {
    requestApi.fetchDataById('movie', id).then(response => {
        //this next sentence will call DiscoveryPage's setState
        //DiscoveryPage is not mounted, so a warning will be raised
        this.props.data(response.data);
    });
};

你可以有这样的东西:

getMoviebyId = (id) => {
    requestApi.fetchDataById('movie', id).then(response => {
        //any component can access this store
        localStorage.setItem("data", response.data);
    });
};

然后,在其他组件或方法中,像这样访问它:

localStorage.getItem("data");

您的代码有点混乱。我有点难以理解您在哪里使用 DiscoveryPagedata,即您在调用 data 方法时设置的那个。但无论如何,希望这会有所帮助。

再补充一点:当使用 localStorage 时,管理它是一种很好的做法,我的意思是当您使用完 data 值时调用 localStorage.removeItem("data")。最好这样做,例如,在某些 componentWillUnmount 方法中。