我如何在 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
。最有可能的是,您的其他组件之一(Movies
、TvShows
等)正在调用您作为 data
道具传递的方法。当这些组件之一被渲染时,DiscoveryPage
组件不被渲染,所以通过 data
属性调用 getDataItem
将导致 DiscoveryPage
的 setState
方法在未呈现时被调用。要解决此问题(假设您想要做的是将数据从组件传递到组件),我建议可能使用 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");
您的代码有点混乱。我有点难以理解您在哪里使用 DiscoveryPage
的 data
,即您在调用 data
方法时设置的那个。但无论如何,希望这会有所帮助。
再补充一点:当使用 localStorage
时,管理它是一种很好的做法,我的意思是当您使用完 data
值时调用 localStorage.removeItem("data")
。最好这样做,例如,在某些 componentWillUnmount
方法中。
我们如何在参数中设置 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
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
。最有可能的是,您的其他组件之一(Movies
、TvShows
等)正在调用您作为 data
道具传递的方法。当这些组件之一被渲染时,DiscoveryPage
组件不被渲染,所以通过 data
属性调用 getDataItem
将导致 DiscoveryPage
的 setState
方法在未呈现时被调用。要解决此问题(假设您想要做的是将数据从组件传递到组件),我建议可能使用 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");
您的代码有点混乱。我有点难以理解您在哪里使用 DiscoveryPage
的 data
,即您在调用 data
方法时设置的那个。但无论如何,希望这会有所帮助。
再补充一点:当使用 localStorage
时,管理它是一种很好的做法,我的意思是当您使用完 data
值时调用 localStorage.removeItem("data")
。最好这样做,例如,在某些 componentWillUnmount
方法中。