【问题标题】:how can I setState in unmount Component in React如何在 React 的卸载组件中设置状态
【发布时间】:2018-12-04 23:09:15
【问题描述】:

我们如何 setState in componentDidMount 挂钩在一个参数中?

我的问题是 React notify 的警告:

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;

【问题讨论】:

  • 看起来有点奇怪。 getDataItem 方法设置新状态。为了清楚起见,您是否期望 getDataItem 设置新状态而不是返回 state.data
  • 嗨@MikhailKatrin,getDataItem 是一个回调函数。当 Movies 组件创建 AJAX 请求然后将数据传递给父组件(本例为 DiscoverPage),然后父组件(DiscoverPage)将接收数据并设置为基本状态。
  • 你能说明如何在任何子组件中调用getDataItem 吗?
  • @KarenGrigoryan 我更新了更多信息。请检查上面。谢谢

标签: javascript reactjs setstate unmount


【解决方案1】:

我认为您缺少从 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>
    );
}
}

【讨论】:

  • 我在App组件中添加了BrowserRouter。
【解决方案2】:

我们可以在 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});
    }
}

【讨论】:

    【解决方案3】:

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

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

    例子:

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

    【讨论】:

    • 是的,但是如何通过这种方式将这些道具之一传递给组件?
    • @PhuongThuan 你可以将数据保存在 store 中,而不是传递 props。
    • @PhuongThuan 如果您遵循 Anu 建议的模式,您将不会通过 props 传递数据。相反,您可以在商店中访问它。任何给定组件都可以读取和写入此值,因此每个组件都具有相同的数据(这在逻辑上类似于我认为您正在尝试做的事情)。
    • @Anu 如何使用 react-router-component 而不是 render func 但仍将道具传递给 Child。孩子会以某种方式从父母那里收到道具?
    • @PhuongThuan 您是否在您的应用程序中使用了一些状态管理库,例如 redux?
    【解决方案4】:

    需要做一些更改,您使用的逻辑效率不高
    就我从你的问题中了解到的那样,这会很好

    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;
    

    【讨论】:

    • 我的 getDataItem 函数接受一个参数。我在 Movies 组件中被称为 AJAX 请求,然后将结果传递给父组件(本例是 DiscoverPage )
    • 逻辑上你应该做相反的事情,你应该在父级中获取数据并将它们传递给子级,如果你希望尝试
    【解决方案5】:

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

    我不明白您所说的“在参数中”是什么意思,但是,回答您的问题,您可以在任何 component... 方法中调用 setState(即使在 componentWillUnmount 中调用它几乎没有意义,因为这个方法只在组件即将被卸载时调用)。

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

    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 方法中。

    【讨论】:

    • 谢谢!它的工作,但更多的问题是我应该将localStorage 放在我的组件中的哪个位置。我把它放在componentDidMount 中,效果很好。我也通过添加这个钩子取消订阅它。 componentWillUnmount() { localStorage.removeItem("data"); } 好吗?
    • 很高兴听到! :) 是的,这就是我建议的方法。当您在另一个组件中仍然需要它时,请注意不要将其删除。尝试找出一种方法,为需要它的组件创建一个父组件,并在那里管理它。还要记住,IHMO 是一种容易出错的模式,所以我还建议考虑其他管理商店的方式,例如反应减少。
    猜你喜欢
    • 2021-10-08
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    • 2020-06-12
    • 1970-01-01
    • 2019-02-17
    • 2019-11-15
    • 2021-02-22
    相关资源
    最近更新 更多