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

Posted

技术标签:

【中文标题】如何在头组件中使用效果(ReactJS/Next.js)【英文标题】:How to useEffect inside header component (ReactJS/Next.js) 【发布时间】:2021-05-23 22:51:19 【问题描述】:

我有一个 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>
  )

【问题讨论】:

【参考方案1】:

通过在setTimeout 中递归调用cycleText,您可以创建一个永不结束的函数,即使Header 组件被卸载(当您更改页面时也会发生这种情况)。当移动到再次有 Header 组件的页面时,会创建该 Header 组件,并在挂载时再次执行一次 useEffect,因此会再次出现无限的 cycleText 函数,等等。

您可以在您的cycleText 函数中放置一个console.log,以查看当您移动到另一个页面并返回到具有标题的页面时它会越来越快地记录。

我认为最好避免递归无限函数以避免使用您的堆栈内存,并改用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 中的事实有关,一些页面是在构建时和一些客户端创建的,因为我在上面的更新中评论了我的疑问。如果不是这样,无论我猜如何,它都会有错误的行为从/到任何页面移动 我发现它是什么,现在我必须了解如何修复它,请看更新2【参考方案2】:

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

在您的标题组件中,保留第一个 useEffect() 不变。像这样添加第二个:

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

这里的关键是数组中的updatedProp 项。这告诉 useEffect 在特定道具或变量更改时触发。将 updatedProp 替换为您希望此 useEffect 触发的变量/道具。

【讨论】:

以上是关于如何在头组件中使用效果(ReactJS/Next.js)的主要内容,如果未能解决你的问题,请参考以下文章

文档准备好时导入 npm (ReactJS/Next.js)

ReactJS/Next.js:CRA 代理不适用于 Next.js(尝试将 API 请求路由到 Express 服务器)

如何为 electron + react js + next 进行生产构建

我应该如何使用React Router来调换我想要在头体和脚中显示的内容?

C++:命名空间——如何在头文件和源文件中正确使用?

如何使用用于 iPhone 开发的 OpenGL ES 渲染 3D 对象(顶点和法线存储在头文件中)?