反应:"Can't perform a React state update on an unmounted component" 没有 useEffect 函数
React: "Can't perform a React state update on an unmounted component" without useEffect function
(我正在使用 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")};
`;
使用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) 你必须组织你的代码
导入顺序:
- 反应+钩子
- 包
- 样式表
- 组件
页面底部的样式化组件
(2) 在网上查看 destructuring props
带有 useEffect 演示的鼠标事件监听器: Stacblitz
基于@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
};
}
}, []);
(我正在使用 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")};
`;
使用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) 你必须组织你的代码
导入顺序:
- 反应+钩子
- 包
- 样式表
- 组件
页面底部的样式化组件
(2) 在网上查看 destructuring props
带有 useEffect 演示的鼠标事件监听器: Stacblitz
基于@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
};
}
}, []);