【问题标题】:React: "Can't perform a React state update on an unmounted component" without useEffect functionReact:“无法在未安装的组件上执行 React 状态更新”,没有 useEffect 功能
【发布时间】:2021-12-14 00:06:38
【问题描述】:

(我正在使用 Next.js + Styled Components,我完全是一个初学者,请帮助我:))

我正在开发一种“Netflix”页面,其中包含不同类型的目录组件。 页面网格中的每个内容都是一个非常复杂的组件,有很多交互,称为 ContentItem.js,在 ContentList.js 中重复。

所以,我收到了这个错误:

Warning: Can't perform a React state update 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 a useEffect cleanup function.
    at ContentItem (webpack-internal:///./ltds/components/Shelf/ContentItem.js:104:62)
    at ul
    at O (webpack-internal:///./node_modules/styled-components/dist/styled-components.browser.esm.js:31:19797)
    at ContentList (webpack-internal:///./ltds/components/Shelf/ContentList.js:52:23)
    at div
    at O (webpack-internal:///./node_modules/styled-components/dist/styled-components.browser.esm.js:31:19797)
    at Shelf (webpack-internal:///./ltds/components/Shelf/Shelf.js:57:66)
    at div
    at SearchResult (webpack-internal:///./pages/search/[term].js:32:70)

但是,在这个组件中,我没有使用 useEffect:

import Image from 'next/image';
import { Paragraph } from '../../styles/Typography';
import styled from 'styled-components';
import { gridUnit } from '../../styles/GlobalStyle';
import { useEffect, useState } from 'react'; 
import { Transition } from 'react-transition-group';
import React from 'react';
import Icon from '../Icon';

const ContentItemContainer = styled.li`
  margin-bottom: 16px;
  text-decoration: none;
  transition: all 0.2s;
  position: relative;
  border-radius: ${(props => props.theme.radius.lg.value)}${gridUnit};
  overflow: hidden;
  height: auto;

  &:hover {
    cursor: pointer;
    transform: ${props => (props.isClicking ? "scale(0.98)" : "scale(1.04)")};
  }
`;
const ItemCover = styled(Image)`
  
  border-radius: ${(props => props.theme.radius.lg.value)}${gridUnit};
  border: 1px solid #504F4E;
  overflow: visible;
  position: relative;

  transition: 0.2s;
  opacity: ${({ state }) => (state === "entering" ? 0 : 1)};
`;

const ItemHoverContainer = styled.div`
  position: absolute;
  z-index: 10;
  top: 0;
  left: 0;
  right: 0;
  padding: 0px;
  margin: 0px;
  height: auto;

  &:hover{
    border: 0.8px solid ${props => (props.theme.alias.image.border.value)};
    border-radius: ${(props => props.theme.radius.lg.value)}${gridUnit};
  }
  
`;

const ItemHoverImage = styled(Image)`
  border-radius: 15px; //15px not 16px: hack to avoid a "phantom line" at the bottom of image

  transition: 0.4s;
  display: ${({ state }) => (state === "exited" ? "none" : "block")};
  opacity: ${({ state }) => (state === "entered" ? 1 : 0)};
 
`;

const IconContainer = styled.div`
  position: absolute;
  left: 41.84%;
  right: 41.13%;
  top: 42.58%;
  bottom: 42.11%;
`;
const DetailsContainer = styled(Paragraph)`

  padding-top: ${({ state }) => (state === "entered" ? props => props.theme.spacing[1].value+gridUnit : 0)};
  transition: 0.4s;
  opacity: ${({ state }) => (state === "entered" ? 1 : 0)};
  height: ${({ state }) => (state === "entered" ? 1 : 0)};
  display: ${({ state }) => (state === "exited" ? "none" : "block")};
  
`;

function ContentItem(props) {

  const nodeRef = React.useRef(null);
  const [isHovering, setIsHovering] = useState(false);
  const [isClicking, setIsClicking] = useState(false);
  const [isLoaded, setIsLoaded] = useState(false);

  const coverSizes = {
    wide:{
      width: 236, 
      height:139
    },
    poster:{
      width: 144, 
      height: 192
    }
  }

  function handleMouseOver(event) {
    setIsHovering(!isHovering)
  }
  function handleMouseOut(event) {
    setIsHovering(!isHovering)
  }

  function handleMouseDown(event) {
    setIsClicking(!isClicking)
  }
  function handleMouseUp(event) {
    setIsClicking(!isClicking)
  }
  function handleLoadingComplete(event) {
    !isLoaded && (setIsLoaded(true))
  }

  return (
    
    <ContentItemContainer isClicking={isClicking} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp}>
      <Transition in={isLoaded} timeout={0} nodeRef={nodeRef}>
      {(state) => ( <div>
        <ItemCover 
            src={props.coverType == "wide" ? props.wideCover : props.posterCover } 
            alt={props.alt} 
            layout={'responsive'}   
            width={props.coverType == "wide" ? coverSizes.wide.width : coverSizes.poster.width} 
            height={props.coverType == "wide" ? coverSizes.wide.height+1 : coverSizes.poster.height}//+1: hack to avoid cut at the bottom of image
            placeholder='blur'
            blurDataURL={props.coverPlaceholder}
            onLoadingComplete={handleLoadingComplete}  
        />
        </div>)}
      </Transition>

      <ItemHoverContainer>
        <Transition in={isHovering} timeout={0} nodeRef={nodeRef} mountOnEnter unmountOnExit>
          {(state) => (
          <div>
            <ItemHoverImage 
              src={props.coverType == "wide" ? props.wideLoopVideo : props.posterLoopVideo }
              layout={'responsive'} 
              width={props.coverType == "wide" ? coverSizes.wide.width : coverSizes.poster.width} 
              height={props.coverType == "wide" ? coverSizes.wide.height : coverSizes.poster.height+1} //+1: hack to avoid a "phantom line" at the bottom of image
              state={state}
            />
            <IconContainer>
              <Icon preserveAspectRatio="xMinYMin meet" name="coverPlay"/>
              </IconContainer>
          </div>
          )}  
        </Transition>
      </ItemHoverContainer>


      <Transition in={props.isDetailed} timeout={100} nodeRef={nodeRef}>
        {(state) => (
          <DetailsContainer state={state} isDetailed={props.isDetailed}>{props.content.details}</DetailsContainer>
        )}
      </Transition>
      

    </ContentItemContainer>

  );
  }

  export default ContentItem

我该如何解决这个问题?

更新

我尝试使用基于@MB_ 答案的useEffect,但仍然发生内存泄漏错误:

import React, { useState, useRef, useEffect } from 'react';
import Image from 'next/image';
import { Transition } from 'react-transition-group';
import styled from 'styled-components';
import { Paragraph } from '../../styles/Typography';
import { gridUnit } from '../../styles/GlobalStyle';

import Icon from '../Icon';

function ContentItem(props) {

  const [isHovering, setIsHovering] = useState(false);
  const [isClicking, setIsClicking] = useState(false);
  const [isLoaded, setIsLoaded] = useState(false);

  const nodeRef = useRef(null);
  const mouseRef = useRef(null);
  const imgRef = useRef(null);

  useEffect(() => {

    const currentMouseRef = mouseRef.current;
    
    if (currentMouseRef) {
      currentMouseRef.addEventListener('mouseover', handleMouseOver);
      currentMouseRef.addEventListener('mouseout', handleMouseOut);
      currentMouseRef.addEventListener('mousedown', handleMouseDown);
      currentMouseRef.addEventListener('mouseup', handleMouseUp);

      return () => {
        currentMouseRef.removeEventListener('mouseover', handleMouseOver);
        currentMouseRef.removeEventListener('mouseout', handleMouseOut);
        currentMouseRef.removeEventListener('mousedown', handleMouseDown);
        currentMouseRef.removeEventListener('mouseup', handleMouseUp);
      };
    }
  }, []);

  const handleMouseOver = () => setIsHovering(true);
  const handleMouseOut = () => setIsHovering(false);
  const handleMouseDown = () => setIsClicking(true);
  const handleMouseUp = () => setIsClicking(false);

  const handleLoadingComplete = () => !isLoaded && setIsLoaded(true);

  const coverSizes = {
    wide:{
      width: 236, 
      height:139
    },
    poster:{
      width: 144, 
      height: 192
    }
  }

  return (
    
    <ContentItemContainer 
      ref={mouseRef} 
      onMouseOver={handleMouseOver} 
      onMouseOut={handleMouseOut} 
      onMouseDown={handleMouseDown} 
      onMouseUp={handleMouseUp}
      isClicking={isClicking} 
    >
      <Transition in={isLoaded} timeout={0} nodeRef={nodeRef}>
      {(state) => ( <div>
        <ItemCover 
            src={props.coverType == "wide" ? props.wideCover : props.posterCover } 
            alt={props.alt} 
            layout={'responsive'}   
            width={props.coverType == "wide" ? coverSizes.wide.width : coverSizes.poster.width} 
            height={props.coverType == "wide" ? coverSizes.wide.height+1 : coverSizes.poster.height}//+1: hack to avoid cut at the bottom of image
            placeholder='blur'
            blurDataURL={props.coverPlaceholder}
            onLoadingComplete={handleLoadingComplete}  
        />
        </div>)}
      </Transition>

      <ItemHoverContainer>
        <Transition in={isHovering} timeout={0} nodeRef={nodeRef} mountOnEnter unmountOnExit>
          {(state) => (
          <div>
            <ItemHoverImage 
              src={props.coverType == "wide" ? props.wideLoopVideo : props.posterLoopVideo }
              layout={'responsive'} 
              width={props.coverType == "wide" ? coverSizes.wide.width : coverSizes.poster.width} 
              height={props.coverType == "wide" ? coverSizes.wide.height : coverSizes.poster.height+1} //+1: hack to avoid a "phantom line" at the bottom of image
              state={state}
            />
            <IconContainer>
              <Icon preserveAspectRatio="xMinYMin meet" name="coverPlay"/>
              </IconContainer>
          </div>
          )}  
        </Transition>
      </ItemHoverContainer>


      <Transition in={props.isDetailed} timeout={100} nodeRef={nodeRef}>
        {(state) => (
          <DetailsContainer state={state} isDetailed={props.isDetailed}>{props.content.details}</DetailsContainer>
        )}
      </Transition>
      

    </ContentItemContainer>

  );
  }

  export default ContentItem

  const ContentItemContainer = styled.li`
  margin-bottom: 16px;
  text-decoration: none;
  transition: all 0.2s;
  position: relative;
  border-radius: ${(props => props.theme.radius.lg.value)}${gridUnit};
  overflow: hidden;
  height: auto;

  &:hover {
    cursor: pointer;
    transform: ${props => (props.isClicking ? "scale(0.98)" : "scale(1.04)")};
  }
`;
const ItemCover = styled(Image)`
  
  border-radius: ${(props => props.theme.radius.lg.value)}${gridUnit};
  border: 1px solid #504F4E;
  overflow: visible;
  position: relative;

  transition: 0.2s;
  opacity: ${({ state }) => (state === "entering" ? 0 : 1)};
`;

const ItemHoverContainer = styled.div`
  position: absolute;
  z-index: 10;
  top: 0;
  left: 0;
  right: 0;
  padding: 0px;
  margin: 0px;
  height: auto;

  &:hover{
    border: 0.8px solid ${props => (props.theme.alias.image.border.value)};
    border-radius: ${(props => props.theme.radius.lg.value)}${gridUnit};
  }
  
`;

const ItemHoverImage = styled(Image)`
  border-radius: 15px; //15px not 16px: hack to avoid a "phantom line" at the bottom of image

  transition: 0.4s;
  display: ${({ state }) => (state === "exited" ? "none" : "block")};
  opacity: ${({ state }) => (state === "entered" ? 1 : 0)};
 
`;

const IconContainer = styled.div`
  position: absolute;
  left: 41.84%;
  right: 41.13%;
  top: 42.58%;
  bottom: 42.11%;
`;
const DetailsContainer = styled(Paragraph)`

  padding-top: ${({ state }) => (state === "entered" ? props => props.theme.spacing[1].value+gridUnit : 0)};
  transition: 0.4s;
  opacity: ${({ state }) => (state === "entered" ? 1 : 0)};
  height: ${({ state }) => (state === "entered" ? 1 : 0)};
  display: ${({ state }) => (state === "exited" ? "none" : "block")};
  
`;

【问题讨论】:

    标签: javascript reactjs memory-leaks next.js styled-components


    【解决方案1】:

    使用EventListeners时需要useEffect

    // (1)
    import React, { useState, useRef, useEffect } from 'react';
    import Image from 'next/image';
    import { Transition } from 'react-transition-group';
    import styled from 'styled-components';
    import { Paragraph } from '../../styles/Typography';
    import { gridUnit } from '../../styles/GlobalStyle';
    
    import Icon from '../Icon';
    
    export default function ContentItem(props) {                 // (2)
      const [isHovering, setIsHovering] = useState(false);
      const [isClicking, setIsClicking] = useState(false);
      const [isLoaded, setIsLoaded] = useState(false);
    
      const nodeRef = useRef(null);
      const mouseRef = useRef(null);               // create another ref for mouse listener
    
    
      useEffect(() => {
        if (mouseRef.current) {
          mouseRef.current.addEventListener('mouseover', handleMouseOver);
          mouseRef.current.addEventListener('mouseout', handleMouseOut);
    
          return () => {
            mouseRef.current.removeEventListener('mouseover', handleMouseOver);
            mouseRef.current.removeEventListener('mouseout', handleMouseOut);
          };
        }
      }, [mouseRef.current]);
    
    
      const handleMouseOver = () => setIsHovering(true);
      const handleMouseOut = () => setIsHovering(false);
      const toggleClick = () => setIsClicking(!isClicking);
      const handleLoadingComplete = () => !isLoaded && setIsLoaded(true);
    
    
      const coverSizes = {
        wide: {
          width: 236,
          height: 139,
        },
        poster: {
          width: 144,
          height: 192,
        },
      };
    
      return (
        <ContentItemContainer ref={mouseRef} onClick={toggleClick}>     // ref + onClick
          <Transition in={isLoaded} timeout={0} nodeRef={nodeRef}>
            {(state) => (                                               // state ?
              <div>
                <ItemCover
                  src={
                    props.coverType == 'wide' ? props.wideCover : props.posterCover
                  }
                  alt={props.alt}
                  layout={'responsive'}
                  width={
                    props.coverType == 'wide'
                      ? coverSizes.wide.width
                      : coverSizes.poster.width
                  }
                  height={
                    props.coverType == 'wide'
                      ? coverSizes.wide.height + 1
                      : coverSizes.poster.height
                  } //+1: hack to avoid cut at the bottom of image
                  placeholder="blur"
                  blurDataURL={props.coverPlaceholder}
                  onLoadingComplete={handleLoadingComplete}
                />
              </div>
            )}
          </Transition>
    
          <ItemHoverContainer>
            <Transition
              in={isHovering}
              timeout={0}
              nodeRef={nodeRef}
              mountOnEnter
              unmountOnExit
            >
              {(state) => (
                <div>
                  <ItemHoverImage
                    src={
                      props.coverType == 'wide'
                        ? props.wideLoopVideo
                        : props.posterLoopVideo
                    }
                    layout={'responsive'}
                    width={
                      props.coverType == 'wide'
                        ? coverSizes.wide.width
                        : coverSizes.poster.width
                    }
                    height={
                      props.coverType == 'wide'
                        ? coverSizes.wide.height
                        : coverSizes.poster.height + 1
                    } //+1: hack to avoid a "phantom line" at the bottom of image
                    state={state}
                  />
                  <IconContainer>
                    <Icon preserveAspectRatio="xMinYMin meet" name="coverPlay" />
                  </IconContainer>
                </div>
              )}
            </Transition>
          </ItemHoverContainer>
    
          <Transition in={props.isDetailed} timeout={100} nodeRef={nodeRef}>
            {(state) => (
              <DetailsContainer state={state} isDetailed={props.isDetailed}>
                {props.content.details}
              </DetailsContainer>
            )}
          </Transition>
        </ContentItemContainer>
      );
    }
    
    // styled components
    const ContentItemContainer = styled.li`
      margin-bottom: 16px;
      text-decoration: none;
      transition: all 0.2s;
      position: relative;
      border-radius: ${(props) => props.theme.radius.lg.value} ${gridUnit};
      overflow: hidden;
      height: auto;
      &:hover {
        cursor: pointer;
        transform: ${(props) => (props.isClicking ? 'scale(0.98)' : 'scale(1.04)')};
      }
    `;
    
    const ItemCover = styled(Image)`
      border-radius: ${(props) => props.theme.radius.lg.value} ${gridUnit};
      border: 1px solid #504f4e;
      overflow: visible;
      position: relative;
      transition: 0.2s;
      opacity: ${({ state }) => (state === 'entering' ? 0 : 1)};
    `;
    
    const ItemHoverContainer = styled.div`
      position: absolute;
      z-index: 10;
      top: 0;
      left: 0;
      right: 0;
      padding: 0px;
      margin: 0px;
      height: auto;
      &:hover {
        border: 0.8px solid ${(props) => props.theme.alias.image.border.value};
        border-radius: ${(props) => props.theme.radius.lg.value} ${gridUnit};
      }
    `;
    
    const ItemHoverImage = styled(Image)`
      border-radius: 15px; //15px not 16px: hack to avoid a "phantom line" at the bottom of image
      transition: 0.4s;
      display: ${({ state }) => (state === 'exited' ? 'none' : 'block')};
      opacity: ${({ state }) => (state === 'entered' ? 1 : 0)};
    `;
    
    const IconContainer = styled.div`
      position: absolute;
      left: 41.84%;
      right: 41.13%;
      top: 42.58%;
      bottom: 42.11%;
    `;
    
    const DetailsContainer = styled(Paragraph)`
      padding-top: ${({ state }) =>
        state === 'entered'
          ? (props) => props.theme.spacing[1].value + gridUnit
          : 0};
      transition: 0.4s;
      opacity: ${({ state }) => (state === 'entered' ? 1 : 0)};
      height: ${({ state }) => (state === 'entered' ? 1 : 0)};
      display: ${({ state }) => (state === 'exited' ? 'none' : 'block')};
    `;
    

    (1) 你必须组织你的代码

    导入顺序:

    1. 反应 + 钩子
    2. 包
    3. 样式表
    4. 组件

    页面底部的样式化组件

    (2) 网上看解构道具

    鼠标事件监听器与 useEffect 演示: Stacblitz

    【讨论】:

    • 谢谢@MB_!它非常有用并且工作正常,但内存泄漏错误仍在发生 + 2 个新警告:1)Error: The ref value 'mouseRef.current' will likely have changed by the time this effect cleanup function runs. 我刚刚在 useEffect 中创建了 currentRef=mouseRef.current 并工作了。 2)React Hook useEffect has an unnecessary dependency: 'mouseRef.current'我删除了依赖数组并开始工作。 Obs.:我更改了您关于切换的建议,因为该组件具有基于“按住鼠标”的行为,将您的逻辑复制到 mouseDown 和 Up 并且可以正常工作。
    • 内存泄漏错误是由useEffect 引起的 => mouseRef.current 是一个必要的依赖项,通常这应该与useEffect 中的const mouseref = useRef (null); 和mouseref.current 一起正常工作(就像在我的演示中一样)。
    【解决方案2】:

    基于@MB_ 逻辑,我在 useEffect 中添加了 setIsLoaded(false) 并且它起作用了 :)

    useEffect(() => {
    
        const currentMouseRef = mouseRef.current;
        
        if (currentMouseRef) {
          currentMouseRef.addEventListener('mouseover', handleMouseOver);
          currentMouseRef.addEventListener('mouseout', handleMouseOut);
          currentMouseRef.addEventListener('mousedown', handleMouseDown);
          currentMouseRef.addEventListener('mouseup', handleMouseUp);
    
          return () => {
            currentMouseRef.removeEventListener('mouseover', handleMouseOver);
            currentMouseRef.removeEventListener('mouseout', handleMouseOut);
            currentMouseRef.removeEventListener('mousedown', handleMouseDown);
            currentMouseRef.removeEventListener('mouseup', handleMouseUp);
            setIsLoaded(false); //Added this here
          };
        }
      }, []);
    

    【讨论】:

      猜你喜欢
      • 2022-01-18
      • 1970-01-01
      • 2019-07-24
      • 1970-01-01
      • 2021-12-03
      • 2020-08-03
      • 2020-09-04
      • 2021-09-28
      • 1970-01-01
      相关资源
      最近更新 更多