【问题标题】:Target Active Link when the route is active in Next.js路由在 Next.js 中处于活动状态时的目标活动链接
【发布时间】:2019-04-15 04:19:09
【问题描述】:

如何像我们在 React-Router-4 中那样定位 Next.js 中的活动链接? 意思是,当它的路由处于活动状态时,给活动链接一个类?

【问题讨论】:

    标签: reactjs react-router next.js


    【解决方案1】:

    一个基于useRouter钩子的简单解决方案:

    import Link from "next/link";
    import { useRouter } from "next/router";
    
    
    export const MyNav = () => {
    
      const router = useRouter();
    
      return (
        <ul>
          <li className={router.pathname == "/" ? "active" : ""}>
            <Link href="/">home</Link>
          </li>
          <li className={router.pathname == "/about" ? "active" : ""}>
            <Link href="/about">about</Link>
          </li>
        </ul>
      );
    };
    
    

    【讨论】:

    • 如果您的链接指向像 &lt;Link href="/#about"&gt; 这样的锚点怎么办。您如何将其设为活动链接?
    • 如果有class组件怎么办?
    • 但是如果你路由到像 www.example.com/blog/xyz 这样的嵌套链接,这个东西就不起作用了。该链接在嵌套链接中处于非活动状态
    • @gurupalsingh 我认为在这种情况下你可以使用router.pathname.startsWith("/about") ? "active" : ""
    • 如果页面中存在参数,此解决方案将不起作用!
    【解决方案2】:

    首先,您需要有一个名为 Link 的组件,其临时属性为 activeClassName

    import { useRouter } from 'next/router'
    import PropTypes from 'prop-types'
    import Link from 'next/link'
    import React, { Children } from 'react'
    
    const ActiveLink = ({ children, activeClassName, ...props }) => {
      const { asPath } = useRouter()
      const child = Children.only(children)
      const childClassName = child.props.className || ''
    
      // pages/index.js will be matched via props.href
      // pages/about.js will be matched via props.href
      // pages/[slug].js will be matched via props.as
      const className =
        asPath === props.href || asPath === props.as
          ? `${childClassName} ${activeClassName}`.trim()
          : childClassName
    
      return (
        <Link {...props}>
          {React.cloneElement(child, {
            className: className || null,
          })}
        </Link>
      )
    }
    
    ActiveLink.propTypes = {
      activeClassName: PropTypes.string.isRequired,
    }
    
    export default ActiveLink
    

    然后有一个导航栏,其中包含创建的组件链接和 css 选择器:active 以区分活动和非活动链接。

    import ActiveLink from './ActiveLink'
    
    const Nav = () => (
      <nav>
        <style jsx>{`
          .nav-link {
            text-decoration: none;
          }
          .active:after {
            content: ' (current page)';
          }
        `}</style>
        <ul className="nav">
          <li>
            <ActiveLink activeClassName="active" href="/">
              <a className="nav-link">Home</a>
            </ActiveLink>
          </li>
          <li>
            <ActiveLink activeClassName="active" href="/about">
              <a className="nav-link">About</a>
            </ActiveLink>
          </li>
          <li>
            <ActiveLink activeClassName="active" href="/[slug]" as="/dynamic-route">
              <a className="nav-link">Dynamic Route</a>
            </ActiveLink>
          </li>
        </ul>
      </nav>
    )
    
    export default Nav
    

    之后,你就可以实现导航栏到你的页面了:

    import Nav from '../components/Nav'
    
    export default () => (
      <div>
        <Nav />
        <p>Hello, I'm the home page</p>
      </div>
    )
    

    这项工作的关键在于组件Link内部,我们将router.pathname的值与来自Link的属性href进行比较,如果值匹配另一个则输入特定的className以使链接看起来激活。

    参考:here

    【讨论】:

    • 在哪里创建链接文件?在我的组件文件夹或根文件夹中?有两个名为“Link”的文件不是不好的做法吗?你有一个来自 next/link,然后是一个名为 link 的组件。
    • @larryburns 你的组件文件夹,恕我直言,只要你清楚你指向哪个链接,但你也可以将链接组件重命名为另一个名称。由你决定,你可以在 index 文件中重新定义它,或者只是重命名组件,使其与 next/link 组件完全不同
    • 但是如果你路由到像 www.example.com/blog/xyz 这样的嵌套链接,这个东西就不起作用了。该链接在嵌套链接中处于非活动状态。
    • 请参阅my answer 了解基于此代码但注意嵌套链接和 url 参数的解决方案
    • 但它不适用于嵌套路由。例如它适用于“/settings”,但不适用于“/settings/locations”。我们如何为多个路由启用与 Active 相同的文本?
    【解决方案3】:

    另一个支持as prop的最小版本:

    import Link from "next/link";
    import {withRouter} from "next/router";
    import {Children} from "react";
    import React from "react";
    
    export default withRouter(({router, children, as, href, ...rest}) => (
       <Link {...rest} href={href} as={as}>
          {React.cloneElement(Children.only(children), {
             className: (router.asPath === href || router.asPath === as) ? `active` : null
          })}
       </Link>
    ));
    

    【讨论】:

    • 我喜欢这个答案,如果你想让孩子不上课而只是追加,请使用:className: router.asPath === href || router.asPath === as ? ${children.props.className} active : children.props.className
    • router.asPath 只在 dev 工作(不知道为什么),我换成 router.pathname === href || router.pathname === as 在生产工作。
    【解决方案4】:

    如果你想使用锚链接试试这个版本的@Rotareti 的代码:

    import Link from "next/link";
    import { useRouter } from "next/router";
    
    
    export const MyNav = () => {
    
      const router = useRouter();
    
      return (
        <ul>
          <li className={router.asPath == "/#about" ? "active" : ""}>
            <Link href="#about">about</Link>
          </li>
        </ul>
      );
    }`;
    

    【讨论】:

      【解决方案5】:

      打字稿版本:

      import React from 'react'
      import Link, { LinkProps } from 'next/link'
      import { useRouter } from 'next/router'
      
      export interface NavLinkProps extends LinkProps {
        children: React.ReactElement
      }
      
      export function NavLink({ children, href, ...props }: NavLinkProps) {
        const router = useRouter()
        return (
          <Link href={href} {...props}>
            {router.pathname === href ? React.cloneElement(children, { 'data-active': true }) : children}
          </Link>
        )
      }
      

      请注意,除非必要,否则我不会克隆孩子。

      【讨论】:

        【解决方案6】:

        这是我的解决方案。我标记了 hrefasPath 道具,然后循环匹配它们。

        您可以选择一个确切的链接(默认)

        <ActiveLink href='/events'>
            <a href='/page'>Page</a>
        </ActiveLink>
        

        或带有fuzzy 属性的模糊链接(匹配/事件)

        <ActiveLink fuzzy href='/events/id'>
            <a href='/events/id'>Event</a>
        </ActiveLink>
        

        这是组件

        import React from 'react';
        import NextLink from 'next/link';
        import { useRouter } from 'next/router';
        
        const ActiveLink = ({ fuzzy = false, href, children }) => {
        const router = useRouter();
        let className = children.props.className || '';
        
        const hrefTokens = href.substr(1).split('/');
        const pathTokens = router.asPath.substr(1).split('/');
        
        let matched = false;
        for (let i = 0; i < hrefTokens.length; i++) {
            if (hrefTokens[i] === pathTokens[i]) {
            matched = true;
            break;
            }
         }
        
         if ((!fuzzy && router.asPath === href) || (fuzzy && matched)) {
            className = `${className} active`;
          }
        
          return (
            <NextLink href={href}>
              {React.cloneElement(children, { className })}
            </NextLink>
          );
        };
        
        export default ActiveLink;
        

        【讨论】:

          【解决方案7】:

          这是另一个带有 NextJS 的 ActiveLink 版本(见下图)

          import { withRouter } from 'next/router';
          import PropTypes from 'prop-types';
          import React from 'react';
          
          const ActiveLink = ({ router, href, isLeftSideBar = false, children }) => {
            const isCurrentPath = router.pathname === href || router.asPath === href;
          
            const handleClick = (e) => {
              e.preventDefault();
              router.push(href);
            };
          
            (function prefetchPages() {
              if (typeof window !== 'undefined') router.prefetch(router.pathname);
            })();
          
            const theme =
              settings.theme === THEMES.LIGHT && isLeftSideBar ? '#e65100' : '#ffeb3b';
            const color = isCurrentPath ? theme : '';
          
            return (
              <a
                href={href}
                onClick={handleClick}
                style={{
                  textDecoration: 'none',
                  margin: 16,
                  padding: 0,
                  fontWeight: isCurrentPath ? 'bold' : 'normal', // I left mine all bold
                  fontSize: 17,
                  color: isLeftSideBar ? '#e65100' : '#ffeb3b',
                }}>
                {children}
              </a>
            );
          };
          
          ActiveLink.propTypes = {
            href: PropTypes.string.isRequired,
            children: PropTypes.any,
          };
          
          export default withRouter(ActiveLink);
          

          在任何地方调用它

          <ActiveLink href='/signup'> Sign Up </ActiveLink>
          

          结果

          【讨论】:

            【解决方案8】:
            //NavItem Wrapper
            import { useRouter } from 'next/router'
            import React from 'react'
            
            const ActiveNav = ({ path, children }) => {
                const router = useRouter();
                const className = router.asPath === `/${path}` ? "active" : '';
                return (
                    <div className={className}>
                        {children}
                    </div>
                )
            }
            
            export default ActiveNav
            

            //在另一个文件中

            import NavbarItem from 'path of ActiveNav component';
            
            const { Header, Content, Footer } = Layout;
            
            const LayoutComponent = (props) => {
            
              return (
               
                  <>
                    <nav className="navigation">
                      <NavbarItem path="">
                        <div className="nav-items">
                          <Link href="/">
                            <a>Home</a>
                          </Link>
                        </div>
                      </NavbarItem>
                      <NavbarItem path="category/game">
                        <div className="nav-items">
                          <Link href="/category/game">
                            <a>Game</a>
                          </Link>
                        </div>
                      </NavbarItem>
                      
                    </nav>
                  <>
                  
              )
            }
            
            export default LayoutComponent
            

            添加样式文件并导入(全局或在 Active Nav 组件中)

             .navigation > .active{
               color:green;
               font:bold;
               // customize according to need
            }
            

            【讨论】:

              【解决方案9】:

              如果存在 URL 参数并检查子页面是否处于活动状态,此解决方案也可以工作。基于 Darryl RN 和 Saman Mohamadi 的回答

              它可以替代 NextJS 链接组件,如果路由或子页面的路由处于活动状态,它会添加“active”和“active-sub”类。

              创建一个名为 Link.js 或任何你喜欢的文件:

              import { withRouter } from "next/router";
              import Link from "next/link";
              import React, { Children } from "react";
              
              export default withRouter(({ router, children, as, href, activeClassName, activeSubClassName, ...rest }) => {
                const child = Children.only(children);
                const childClassName = child.props.className || "";
                // remove URL parameters
                const sanitizedPath = router.asPath.split("#")[0].split("?")[0];
                // activeClassName and activeSubClassName are optional and default to "active" and "active-sub"
                const activeClass = activeClassName || "active";
                const activeSubClass = activeSubClassName || "active-sub";
                // remove trailing slash if present
                href = href && href !== "/" && href.endsWith("/") ? href.slice(0, -1) : href;
                as = as && as !== "/" && as.endsWith("/") ? as.slice(0, -1) : as;
                // check if the link or a sub-page is active and return the according class name
                const activityClassName = sanitizedPath === href || sanitizedPath === as ? activeClass : sanitizedPath.startsWith(href + "/") || sanitizedPath.startsWith(as + "/") ? activeSubClass : "";
                // combine the child class names with the activity class name
                const className = `${childClassName} ${activityClassName}`.trim();
                return (
                  <Link href={href} as={as} {...rest}>
                    {React.cloneElement(child, {
                      className: className || null,
                    })}
                  </Link>
                );
              });
              

              通过

              将其导入您的文件中
              import Link from "./Link.js";
              

              或任何你喜欢的名字

              import ActiveLink from "./Link.js";
              

              并像使用 NextJS "Link" 组件(next/link)一样使用它:

              <Link href="/home">
                <a className="link-classname">Home</a>
              </Link>
              

              它将默认为“active”和“active-sub”类名,但您可以设置自定义类名:

              <Link href="/home" activeClassName="my-active-classname" activeSubClassName="another-classname">
                <a className="link-classname">Home</a>
              </Link>
              

              如果您不需要某个活动类,请在字符串中放置一个空格:

              <Link href="/home" activeSubClassName=" ">
                <a className="link-classname">Home</a>
              </Link>
              

              【讨论】:

                【解决方案10】:

                只要在里面放一个a标签...

                <Link href={href}>
                  <a className='text-red-400 active:text-red-800'>{children}</a>
                </Link>
                

                【讨论】:

                • OP 没有提到 Tailwind 或任何 CSS 框架,关于 active,说 “在其路由处于活动状态时为活动链接提供一个类”。这不是 CSS active 伪类所做的。
                猜你喜欢
                • 1970-01-01
                • 2018-03-24
                • 2023-03-06
                • 1970-01-01
                • 2017-07-13
                • 1970-01-01
                • 2017-04-11
                • 1970-01-01
                • 2016-10-09
                相关资源
                最近更新 更多