卸载组件后反应更新状态(内存泄漏)

React updating state after component has been unmounted (memory leak)

我正在构建一个 React 应用程序(我是 javascript 的菜鸟),并且在尝试在卸载组件后更新状态时努力应对 React。

React 应用程序的上下文:作为学校项目的一部分,我必须为一个小型剧院综合体构建电影票预订服务,在网络应用程序的主页上,我有一张图片 'carousel'每 5 秒更新一次图像。

这是正在运行的主页:https://apollo-9dd16.web.app/

(图像加载时间只是因为我选择了相当大的图像,将被修复) - 首次访问页面时的加载屏幕也是因为从 firebase 加载所有内容需要很长时间(所有内容都通过动态更新firebase 数据库)如果有办法让它更快,请告诉我

此外,我浏览了尽可能多的 SO 帖子,每个人都建议在执行状态更改之前添加检查组件是否已安装,我想我已经完成了吗?

不管怎样,在问题上,出于某种原因,它会说我有内存泄漏,因为我试图在卸载后更改组件状态(我无法轻易重现它,但它已经发生了多次)

import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import MoviesContext from '../contexts/Movies'


/* Todo: 

 - Work on the transition between movies
 - Work on sending the user to the right place on book now button press

*/

class Home extends Component {
    static contextType = MoviesContext
    state = {
        intervalID:0,
        index:1,
        prevIndex:0,
        nextIndex:2,
        picList:[]
    }
    componentDidMount() {
        let mounted = true
        const posterURLS = [] // eslint-disable-next-line
        this.context.map((movies) => {
            posterURLS.push(movies.posterURL)
        })
        this.setState({picList: posterURLS})

        if(mounted) {
            const newIntervalId = setInterval(() => {
                if (this.state.nextIndex + 1 === this.state.picList.length ){
                    this.setState({ 
                        prevIndex: this.state.index,
                        index: this.state.nextIndex,
                        nextIndex: 0
                    })
                } else {
                    this.setState({
                        prevIndex: this.state.index,
                        index: this.state.nextIndex,
                        nextIndex: this.state.nextIndex + 1
                    })
                }
            }, 5000);
        
        
            this.setState(prevState => {
                return {
                    ...prevState,
                    intervalId: newIntervalId,
                };
            });

        }
        return () => mounted = false
    }
    render() {
        return (
            <div className="font-sans font-light text-theme-white text-4xl z-10 flex justify-center items-center h-screen">
                <div className="h-3/4">
                    <div className="h-full justify-center items-center">
                        <div className="h-full hidden md:flex">
                            <img src={this.state.picList[this.state.prevIndex]} alt="this is a movie" className="h-full rounded-2xl -mx-20"/>
                            <img src={this.state.picList[this.state.nextIndex]} alt="this is a movie" className="h-full rounded-2xl -mx-20"/>
                        </div>
                        <div className="absolute inset-10 flex justify-center items-center h-screen">
                            <img src={this.state.picList[this.state.index]} alt="this is a movie" className="h-3/4 rounded-2xl"/>
                        </div>
                    </div>
                    <div className="absolute inset-0 top-10 flex justify-center items-center">
                        <Link to="/book" className="bg-theme-light text-theme-black rounded-2xl py-3 px-5 hover:bg-theme-black hover:text-theme-light">Book Now</Link>
                    </div>
                </div>
                
            </div>  
        )  
    }

}

export default Home

你非常接近!发生这种情况的原因是因为这段代码:

/* 1 */  if (mounted) {
/* 2 */    const newIntervalId = setInterval(() => {
/* 3 */      if (this.state.nextIndex + 1 === this.state.picList.length) {
/* 4 */        this.setState({
/* 5 */          prevIndex: this.state.index,
/* 6 */          index: this.state.nextIndex,
/* 7 */          nextIndex: 0,
/* 8 */        });
/* 9 */      } else {
/* 10 */        this.setState({
/* 11 */          prevIndex: this.state.index,
/* 12 */          index: this.state.nextIndex,
/* 13 */          nextIndex: this.state.nextIndex + 1,
/* 14 */        });
/* 15 */      }
/* 16 */    }, 5000);
/* 17 */  
/* 18 */    this.setState((prevState) => {
/* 19 */      return {
/* 20 */        ...prevState,
/* 21 */        intervalId: newIntervalId,
/* 22 */      };
/* 23 */    });
/* 24 */  }
/* 25 */  

第 1 行(在上面的代码段中)的 if (mounted) 条件有点过高。您应该将 if 语句包裹在第 4 行和第 10 行的 setState 调用周围,因为它们都在 setInterval 函数回调中。

目前,代码是这样的:

  • 如果组件已挂载
  • 注册这个每 5 秒更新一次状态的间隔

但我相信您希望您的代码说明:

  • 如果组件已挂载
  • 每 5 秒更新一次状态,但前提是组件仍处于挂载状态
if (mounted) {
  const newIntervalId = setInterval(() => {
    if (this.state.nextIndex + 1 === this.state.picList.length) {
      if (mounted) {
        this.setState({
          prevIndex: this.state.index,
          index: this.state.nextIndex,
          nextIndex: 0,
        });
      }
    } else {
      if (mounted) {
        this.setState({
          prevIndex: this.state.index,
          index: this.state.nextIndex,
          nextIndex: this.state.nextIndex + 1,
        });
      }
    }
  }, 5000);

  this.setState((prevState) => {
    return {
      ...prevState,
      intervalId: newIntervalId,
    };
  });
}

这应该可以修复卸载组件后的组件更新。您甚至可以删除上述代码段开头的 if (mounted) 检查。

它发生的原因是在执行 componentDidMount 函数时 mounted 变量是 true,所以它会注册 setInterval 回调和保持每 5 秒执行一次。

虽然在执行 setInterval 回调时 mounted 变量可能不再是 true,但没有什么可以阻止它执行。