为什么不能在componentWillUnmount生命周期方法中调用setState?
React中禁止在componentWillUnmount调用setState的规则说明
含义解释
首先官方原文的核心逻辑非常直白:
你不应该在componentWillUnmount()中调用setState(),因为该组件永远不会被重新渲染。一旦组件实例被卸载,它将永远不会被再次挂载
简单来说就是,当componentWillUnmount触发时,组件已经进入销毁流程,此时更新组件状态没有任何实际作用,属于无效操作。
背后的运行逻辑
- React的生命周期钩子执行顺序是固定的,componentWillUnmount是组件从DOM树中移除前触发的最后一个钩子,一旦执行完这个钩子,组件实例就会被销毁,DOM节点也会被移除,不存在后续的重渲染流程。
- setState的本质是向React提交状态更新请求,React会将更新入队后调度重渲染,最终把状态变更同步到DOM上。而组件卸载后不可能再触发重渲染,此时调用setState只会产生无意义的状态更新入队操作,浪费性能。
- 多数情况下在componentWillUnmount中调用setState都意味着代码存在隐患:通常是异步任务(网络请求、定时器、事件订阅等)没有被正确清理,在组件卸载后才触发回调尝试更新状态,React 17及之后的版本会针对这种情况抛出专门的警告,提示可能存在内存泄漏。
代码示例
错误用法示例
class TodoList extends React.Component { state = { list: [] }; componentDidMount() { this.timer = setInterval(() => { fetch('/api/todo/list') .then(res => res.json()) .then(data => this.setState({ list: data })) }, 3000) } componentWillUnmount() { // 错误:此处调用setState完全无效,不会触发任何视图更新 this.setState({ list: [] }); } render() { return <ul>{this.state.list.map(item => <li key={item.id}>{item.content}</li>)}</ul> } }
上面的代码中,componentWillUnmount里的setState执行后,组件马上就会被销毁,状态更新不会被处理,也不会反映到视图上。
正确的卸载清理写法
componentWillUnmount的设计初衷就是用来做清理操作,应该在这里终止异步任务、解绑事件、取消订阅等:
class TodoList extends React.Component { state = { list: [] }; controller = new AbortController(); componentDidMount() { this.timer = setInterval(() => { fetch('/api/todo/list', { signal: this.controller.signal }) .then(res => res.json()) .then(data => this.setState({ list: data })) }, 3000) } componentWillUnmount() { // 正确:清理定时器,取消未完成的网络请求 clearInterval(this.timer); this.controller.abort(); } render() { return <ul>{this.state.list.map(item => <li key={item.id}>{item.content}</li>)}</ul> } }
内容的提问来源于stack exchange,提问作者Anatoliy Kiguradze
相关产品推荐
相关产品推荐

