如何在头部组件中使用useEffect (ReactJS/Next.js)

3

我有一个名为header.js的组件,它在我的next.js/react项目的每个页面上都存在。

在组件内部,我创建了一个打字机效果及其旋转。为了启动它,我将代码放在useEffect中:

export default function Header () {

  useEffect(() => {

    let delay = 2200;
    let input = document.getElementsByClassName('myButton')[0],
        index = 0;
    const cycleText = () =>{
      let randomItem = items[Math.floor(Math.random() * items.length)];

      button.setAttribute('value', "Click " + randomItem);
      index++;
      (index === items.length) ? index = 0 : "";
      setTimeout(cycleText, delay);
    }

    cycleText();
  }, []);

  return ( ...

为了重新创建像jQuery中的 document.ready,我正在尝试使用useEffect,但是我遇到了两个问题。一是如果我切换页面,标题仍然保持不变,但是特效代码好像失效了(不再展示文本),二是如果我回到首页,代码在第一次加载时可以正常工作,但现在会快速连续显示两个旋转实例(就好像有两个useEffect同时运行在一个以上,或者类似的内容)。
我正在逐渐接近理解 useEffect 如何工作,我知道 [ ] 会使其在页面加载时运行一次,但我认为这对我的情况就足够了,看起来我错了。我是否应该传递 [] 内的某些东西,以解决上述两个问题?我已经尝试过next.js路由器,并通过传递 router.pathname 让其在每次页面更改时重新运行,但没有奏效。
更新: 我发现上述两种奇怪的行为只会在从主页到预生成页面(通过 staticPaths 和 staticProps)并返回时发生。 如果我从主页转到用户仪表板(通过服务器端渲染)并返回,则不会影响标题中的 useEffect
更新2: 我发现是我在_app.js 中的保留滚动位置代码实际上针对预生成页面以及主页,但实际上并没有影响用户仪表板页面。现在我必须找出如何通过让组件重新渲染来保留滚动位置,因为我已经发现由于相同的问题,另一段代码使侧边栏粘性无法生效。
这是我的_app.js代码:
import React, { useRef, useEffect, memo } from 'react'
import { Provider as NextAuthProvider } from 'next-auth/client'
import { SWRConfig } from 'swr'
import Router, { useRouter } from 'next/router'
import '../public/style.scss'
import { AnimatePresence } from "framer-motion"

export default function MyApp ({ Component, pageProps }) {

  const router = useRouter()
  const retainedComponents = useRef({})

  var isRetainableRoute = false

  if (router.pathname == '/' || router.asPath.includes('/author/') || router.asPath.includes('/video/')){
    isRetainableRoute = true
  }

  // Add Component to retainedComponents if we haven't got it already
  if (isRetainableRoute && !retainedComponents.current[router.asPath]) {
    const MemoComponent = memo(Component)
    retainedComponents.current[router.asPath] = {
      component: <MemoComponent {...pageProps} />,
      scrollPos: 0
    }
  }

  // Save the scroll position of current page before leaving
  const handleRouteChangeStart = url => {
    if (isRetainableRoute) {
      retainedComponents.current[router.asPath].scrollPos = window.scrollY
    }
  }

  // Save scroll position - requires an up-to-date router.asPath
  useEffect(() => {
    router.events.on('routeChangeStart', handleRouteChangeStart)
    return () => {
      router.events.off('routeChangeStart', handleRouteChangeStart)
    }
  }, [router.asPath])

  // Scroll to the saved position when we load a retained component
  useEffect(() => {
    if (isRetainableRoute) {
      window.scrollTo(0, retainedComponents.current[router.asPath].scrollPos)
    }
  }, [Component, pageProps])

  return (
    <NextAuthProvider
      options={{
        clientMaxAge: 60,
        keepAlive: 61
      }}
      session={pageProps.session}
      >
      <SWRConfig 
        value={{
          revalidateOnFocus: false
        }}
      >
        <AnimatePresence exitBeforeEnter>
          <div>
            <div style={{ display: isRetainableRoute ? 'block' : 'none' }}>
              {Object.entries(retainedComponents.current).map(([path, c]) => (
                <div
                  key={path}
                  style={{ display: router.asPath === path ? 'block' : 'none' }}
                >
                  {c.component}
                </div>
              ))}
            </div>
            {!isRetainableRoute && <Component {...pageProps} />}
          </div>
        </AnimatePresence>
      </SWRConfig>
    </NextAuthProvider>
  )
}
2个回答

3
通过在setTimeout中递归调用cycleText,您创建了一个永远不会结束的函数,即使Header组件被卸载(当您更改页面时发生)。 当移动到再次具有Header组件的页面时,该Header组件被创建,并且在挂载时再次执行useEffect,因此启动另一个无限循环的cycleText函数等。
您可以在cycleText函数中放置console.log以查看当您移动到另一页并返回具有Header的页面时,它将记录得更快。
我认为最好避免使用递归无限函数以避免使用堆栈内存,并改用setInterval。 更重要的是:不要忘记在组件被卸载时停止您的函数。 对于useEffect,当其返回时完成,您可以在那里调用clearInterval
您可以这样做:
  useEffect(() => {
    let delay = 2200;
    let input = document.getElementsByClassName('myButton')[0],
    index = 0;

    const interval = setInterval(() => {
      let randomItem = items[Math.floor(Math.random() * items.length)];
      button.setAttribute('value', "Click " + randomItem);
      index++;
      if(index >= items.length){
        index = 0
      }
    }, delay);

    return () => {
      clearInterval(interval);
    };
  }, []);

很遗憾它不起作用,还是要谢谢你。我使用它是因为它比超时更有意义。但我担心这更多与 Next.js 中的一些页面在构建时创建,而另一些页面在客户端上创建有关,正如我在上面的更新中所评论我的疑问一样。如果不是这样,那么无论如何,它都会移动从/到任何页面时出现错误行为。 - m4tt
我已经找出了问题所在,现在需要理解如何修复它,请查看更新2。 - m4tt

0

你可以在组件中声明多个useEffect()。我认为这会对你有所帮助。

在你的头部组件中,保留第一个useEffect()不变。添加第二个如下:

useEffect(() => {
  ... code to update state
}, [updatedProp]);

重点在于数组中的updatedProp项。这告诉useEffect当特定的prop或变量发生更改时触发。将updatedProp替换为您想要此useEffect触发的变量/props。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接