登录触发卸载组件调用setState警告,如何无需withRouter获取路由props?
withRouter访问props 每次登录我都会收到如下警告:
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.
以下是我的代码:
authpage.js
handleLoginSubmit = (e) => { e.preventDefault() let { email,password } = this.state const data = { email : email, password : password } fetch('http://localhost:3001/auth/login',{ method : 'post', body : JSON.stringify(data), headers : { "Content-Type":"application/json" } }).then(res => res.json()) .then(data => { if(data.success){ sessionStorage.setItem('userid',data.user.id) sessionStorage.setItem('email',data.user.email) } this.setState({loginData : data, userData : data, email:"", password:""}) if(data.token) { Auth.authenticateUser(data.token) this.props.history.push('/dashboard') } this.handleLoginMessage() this.isUserAuthenticated() }) } export default withRouter(AuthPage)
我使用withRouter来获取props,以此通过this.props.history.push('/dashboard')实现页面导航,如果不使用withRouter则无法访问该props。
index.js
const PrivateRoute = ({ component : Component, ...rest }) => { return ( <Route {...rest} render = { props => ( Auth.isUserAuthenticated() ? ( <Component {...props} {...rest} /> ) : ( <Redirect to = {{ pathname: '/', state: { from: props.location } }}/> ) )}/> ) } const PropsRoute = ({ component : Component, ...rest }) => ( <Route {...rest} render = { props => ( <Component {...props} {...rest} /> )}/> ) const Root = () => ( <BrowserRouter> <Switch> <PropsRoute exact path = "/" component = { AuthPage } /> <PrivateRoute path = "/dashboard" component = { DashboardPage } /> <Route path = "/logout" component = { LogoutFunction } /> <Route component = { () => <h1> Page Not Found </h1> } /> </Switch> </BrowserRouter> )
我怀疑问题出在withRouter上,请问如何不使用withRouter就能访问this.props?
先帮你理清两个核心问题:内存泄漏警告的根源和不用withRouter获取history的方法,咱们一步步来解决。
一、为什么会出现内存泄漏警告?
其实和withRouter关系不大!问题出在你的登录请求是异步的:当this.props.history.push('/dashboard')执行后,AuthPage组件已经被卸载了,但后续的then回调里还在调用this.setState——你在操作一个已经不存在的组件状态,自然会触发这个警告。
二、不用withRouter也能拿到history的方法
看你的index.js里已经定义了PropsRoute,它的render函数已经把Route自带的props(包括history、location、match)传递给了组件!所以你完全不需要withRouter。
只要把export default withRouter(AuthPage)改成export default AuthPage,AuthPage里就能直接访问this.props.history了——因为PropsRoute已经帮你把所有需要的props传进来了。
三、修复内存泄漏警告
解决了props的问题,咱们来彻底搞定内存泄漏,有两种常用方法:
方法1:用标志位判断组件是否挂载
在组件里加一个状态位,记录组件是否还处于挂载状态,异步回调里只有组件挂载时才执行setState:
class AuthPage extends React.Component { _isMounted = false; componentDidMount() { this._isMounted = true; } componentWillUnmount() { this._isMounted = false; } handleLoginSubmit = (e) => { e.preventDefault() let { email,password } = this.state const data = { email : email, password : password } fetch('http://localhost:3001/auth/login',{ method : 'post', body : JSON.stringify(data), headers : { "Content-Type":"application/json" } }).then(res => res.json()) .then(data => { // 组件已卸载就直接返回,不执行后续操作 if (!this._isMounted) return; if(data.success){ sessionStorage.setItem('userid',data.user.id) sessionStorage.setItem('email',data.user.email) } this.setState({loginData : data, userData : data, email:"", password:""}) if(data.token) { Auth.authenticateUser(data.token) this.props.history.push('/dashboard') } this.handleLoginMessage() this.isUserAuthenticated() }) } // ...你的其他组件代码 export default AuthPage; }
方法2:用AbortController取消未完成的请求
如果想更彻底,可以用AbortController在组件卸载时直接取消未完成的fetch请求:
handleLoginSubmit = (e) => { e.preventDefault() let { email,password } = this.state const data = { email : email, password : password } // 创建AbortController实例 const controller = new AbortController(); const signal = controller.signal; fetch('http://localhost:3001/auth/login',{ method : 'post', body : JSON.stringify(data), headers : { "Content-Type":"application/json" }, signal: signal // 绑定取消信号 }).then(res => res.json()) .then(data => { if(data.success){ sessionStorage.setItem('userid',data.user.id) sessionStorage.setItem('email',data.user.email) } this.setState({loginData : data, userData : data, email:"", password:""}) if(data.token) { Auth.authenticateUser(data.token) this.props.history.push('/dashboard') } this.handleLoginMessage() this.isUserAuthenticated() }) .catch(err => { // 处理请求被取消的情况 if (err.name === 'AbortError') { console.log('Login request was aborted'); } }) // 保存取消请求的方法 this._cancelRequest = () => controller.abort(); } componentWillUnmount() { // 组件卸载时取消请求 if (this._cancelRequest) { this._cancelRequest(); } }
最后总结一下:
- 你的
PropsRoute已经帮你传递了Route的props,去掉withRouter直接用this.props.history就行 - 内存泄漏是因为异步回调在组件卸载后执行
setState,用标志位或AbortController就能解决
内容的提问来源于stack exchange,提问作者tony chen

