ReactJS re-render问题:useEffect导致页面每4秒重新加载
问题描述
我在Footer组件中遇到了奇怪的行为。每4秒,useEffect会重新渲染整个页面和组件。它应该只更改那个元素。不确定我做错了什么。
当注释掉CookieBanner组件时,这种情况就不会发生。同时,数据的控制台日志每4秒被触发一次。
"use client"
import type { SettingsPayload } from "types"
import { useEffect, useRef, useState } from "react"
import { usePathname } from "next/navigation"
import { Button, IconsLibrary, ThemeSwitch } from "@/components/shared"
import { CookieBanner } from "@/components/shared/CookieBanner"
import { CustomPortableText } from "@/components/shared/CustomPortableText"
interface FooterProps {
data: SettingsPayload
}
const footerData = {
currentProjects: {
amount: 4,
projects: [
{
projectName: "Aantal",
description: "lopende projecten"
},
{
projectName: "Zorgwijzer",
description: "lopende projecten"
},
{
projectName: "Energievergelijk",
description: "Dé vergelijkingssite voor creditcards"
},
{
projectName: "Creditcard",
description: "lopende projecten"
}
]
}
}
const Footer = ({ data }: FooterProps) => {
const pathname = usePathname()
if (!data) return null
const [currentProject, setCurrentProject] = useState(0)
const {
socials,
email,
cookieBanner,
phone,
c2a_internalLink,
page_title,
address,
company_details,
marquee_text
} = data
const intervalRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
intervalRef.current = setInterval(() => {
setCurrentProject((prevIndex) => (prevIndex + 1) % footerData.currentProjects.projects.length)
}, 4000)
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
}
}, [])
const navArray = Array.from(
{ length: footerData.currentProjects.projects.length },
(_, index) => {
// Create your element here based on the index or originalArray
return `Element {index}`
}
)
const Socials = () => {
return (
socials &&
socials.length && (
<div className="mb-[40px] grid grid-cols-2 gap-[4px] md:mb-0 xl:grid-cols-3">
{socials.map((social) => {
return (
<Button
variant="primary"
className="w-full min-w-[130px] max-w-[130px] border-1 border-black bg-transparent px-6 py-[7px] text-center text-base leading-none"
type="link-external"
url={social.url}
key={social.name}
>
{social.name}
</Button>
)
})}
</div>
)
)
}
return (
<>
{pathname !== "/" && data && (
<div className="grid grid-cols-6 gap-x-[20px] bg-primary px-[10px] pb-[20px] pt-[20px] md:px-10 md:pb-[50px] lg:grid-cols-footer">
<div className="col-span-6 flex h-[400px] flex-col items-start rounded-[25px] bg-[#40e643] px-[30px] pb-[21px] pt-[32px] md:px-[50px] md:pb-[24px] md:pt-[35px] lg:col-auto">
{marquee_text && (
<h2 className="mb-[40px] flex h-[44px] max-w-[244px] overflow-hidden whitespace-nowrap rounded-[100px] bg-black text-3xl text-white">
<div className="flex animate-marquee items-center justify-between whitespace-nowrap font-medium">
{marquee_text}
</div>
</h2>
)}
<div>
{email && <h3 className="text-3xl font-medium leading-tight text-black">{email}</h3>}
{phone && <h3 className="text-3xl font-medium leading-tight text-black">{phone}</h3>}
</div>
{c2a_internalLink && (
<Button
variant="primary"
className="mt-[32px] flex items-center justify-between gap-x-4 rounded-[12px] border-2 border-black bg-transparent font-medium"
iconType="sm-arrow-right"
iconClassName="ml-0"
type="link-internal"
url={c2a_internalLink.slug}
>
{page_title ? page_title : c2a_internalLink.title}
</Button>
)}
<div className="mt-[70px] flex w-full justify-between text-[14px] text-black">
<span>BrightBunch</span>
<span>© 2015 - 2023</span>
</div>
</div>
<div className="col-span-6 mt-[10px] flex flex-col items-start justify-between rounded-[25px] bg-[#a5a5a5] px-[30px] py-[30px] md:col-span-4 md:px-[50px] md:pb-[30px] md:pt-[40px] lg:col-auto lg:mt-0">
<Socials />
<div className="flex w-full flex-col justify-between gap-[16px] text-[14px] text-black md:flex-row">
{address && (
<div className="flex flex-col">
<CustomPortableText value={address} />
</div>
)}
{company_details && (
<div className="flex flex-col">
<CustomPortableText value={company_details} />
</div>
)}
</div>
</div>
<div className="col-span-6 mt-[10px] flex flex-row gap-[10px] md:col-span-2 md:flex-col md:gap-[20px] lg:col-auto lg:mt-0">
<div className="flex h-full w-1/2 items-center justify-center rounded-[25px] bg-[#313131] md:h-[100px] md:w-full">
<ThemeSwitch
wrapperClass="flex h-full w-full"
buttonClass="w-full h-full flex justify-center place-content-center items-center"
/>
</div>
<div className="flex grow flex-col items-center justify-between rounded-[25px] bg-[#313131] p-[15px]">
<h2 className="mt-[16px] font-base text-[42px] text-white md:mt-[20px] md:text-[62px]">
{footerData.currentProjects.amount}
</h2>
<div className="relative mt-[17px] h-[60px] w-full text-center md:mt-[20px]">
{footerData.currentProjects.projects.map((project, index) => {
return (
<div
key={project.projectName}
className={`absolute left-0 top-0 h-full w-full text-2xl leading-tight transition-all duration-2000 ease-in-out{
index === currentProject ? "opacity-100" : "pointer-events-none opacity-0"
}`}
>
{currentProject === index ? (
<div className="text-[14px] text-white">
<h3 className="text-[14px] text-[#a5a5a5]">{project.projectName}</h3>
<span>{project.description}</span>
</div>
) : (
<></>
)}
</div>
)
})}
</div>
<div className="mt-2 flex">
{navArray.map((el, index) => {
return (
<div
key={el}
className={`mr-[8px] h-[6px] rounded-[100px] transition-all duration-1000 ease-in-out hover:cursor-pointer ${
index === currentProject ? "w-[20px] bg-white" : "w-[6px] bg-[#737373]"
}`}
onClick={() => setCurrentProject(index)}
/>
)
})}
</div>
</div>
</div>
</div>
)}
<CookieBanner data={cookieBanner} />
</>
)
}
export default Footer
CookieBanner.tsx:
"use client"
import Link from "next/link"
import CookieConsent from "react-cookie-consent"
type CookieBannerProps = {
data?: CookieBanner
}
type CookieBannerButtonProps = {
children: React.ReactNode
onClick: () => void
}
export async function CookieBanner({ data }: CookieBannerProps) {
if (!data || !data.enabled) {
return null
}
const { title, buttonText, privacy_link_text, privacy_url } = data
const debug = process.env.NODE_ENV === "development"
const ButtonComponent = ({ children, onClick }: CookieBannerButtonProps) => {
return (
<div className="flex items-center gap-5">
{privacy_url && (
<Link
href={`/${privacy_url}`}
className="flex items-center gap-2 text-sm text-white lg:text-base "
>
{privacy_link_text ? privacy_link_text : "Privacy Policy"}
</Link>
)}
<button
onClick={onClick}
className="inline-flex items-center text-sm text-white lg:text-base"
>
<span className="pl-3">{children}</span>
</button>
</div>
)
}
return (
<CookieConsent
disableStyles
location="bottom"
buttonText={buttonText ? buttonText : "Accepteren"}
cookieName="CookieConsent"
expires={365}
debug={debug}
containerClasses="bg-black text-white justify-between flex flex-col sm:flex-row px-6 py-4 w-full sm:items-center fixed gap-3 bottom-0 left-0 w-full z-40"
ButtonComponent={ButtonComponent}
>
<h3 className="text-lg sm:text-xl">{title}</h3>
</CookieConsent>
)
}
解决方案
如果您只想更新组件中的特定元素,而不会导致整个组件或页面重新渲染,您可以使用React的useMemo
或useCallback
hooks来记忆您想要更新的组件部分。这可以帮助优化您的组件并防止不必要的重新渲染。
以下是您可以修改组件以实现此目的的方法:
import React, { useState, useEffect, useCallback } from 'react';
function YourComponent() {
const [currentProject, setCurrentProject] = useState(0);
const projects = footerData.currentProjects.projects;
// Memoize the component rendering this specific element
const projectElement = useCallback(() => {
return (
<div>
{/* Your element that you want to update */}
{projects[currentProject]}
</div>
);
}, [currentProject, projects]);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCurrentProject((prevIndex) => (prevIndex + 1) % projects.length);
}, 4000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
}
}, [projects]);
return (
<div>
{/* Render the memoized element */}
{projectElement()}
</div>
);
}
export default YourComponent;