简体   繁体   English

当路由在 Next.js 中处于活动状态时定位活动链接

[英]Target Active Link when the route is active in Next.js

How to target the active Link in Next.js like they way we do it in React-Router-4?如何像我们在 React-Router-4 中那样以 Next.js 中的活动链接为目标? Meaning, give the active link a class when its route is active?意思是,在路由处于活动状态时给活动链接一个类?

A simple solution based on the useRouter hook:基于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>
  );
};

First, you need to have a component called Link, with temporary attribute activeClassName首先,您需要有一个名为 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

Then have a navigation bar with created component Link and css selector :active to differentiate between active and inactive link.然后有一个带有创建的组件链接和 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

After that, you can implement the navigation bar to your page:之后,您可以将导航栏实现到您的页面:

import Nav from '../components/Nav'

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

The key of how does this work is located inside component Link, we compare the value of router.pathname with attribute href from the Link, if the value match the other then put specific className to make the link looks activated.这个工作的关键是在组件Link里面,我们将router.pathname的值和Link的href属性进行比较,如果匹配到另一个就输入具体的className,让link看起来是激活的。

Reference: here参考: 这里

Another minimal version which supports as prop:另一个支持as道具的最小版本:

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>
));

If you want to use an anchor Link try this version of @Rotareti 's code:如果您想使用锚链接,请尝试使用此版本的 @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>
  );
}`;

Typescript version:打字稿版本:

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>
  )
}

Note that I'm not cloning the child unless necessary.请注意,除非必要,否则我不会克隆孩子。

Here is a solution that also works if URL-parameters are present and checks if a sub-page is active.这是一个解决方案,如果存在 URL 参数并检查子页面是否处于活动状态,则该解决方案也有效。 Based on the answers by Darryl RN and Saman Mohamadi基于 Darryl RN 和 Saman Mohamadi 的回答

It works as a drop-in replacement for the NextJS link component and adds the classes "active" and "active-sub" if the route or the route of a subpage is active.它可以作为 NextJS 链接组件的替代品,并在路由或子页面的路由处于活动状态时添加类“active”和“active-sub”。

Create a file called Link.js or whatever you like:创建一个名为 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 it in your files via通过将其导入到您的文件中

import Link from "./Link.js";

or with any name you like或任何你喜欢的名字

import ActiveLink from "./Link.js";

and use it as you would use the NextJS "Link" component (next/link):并像使用 NextJS“链接”组件(下一个/链接)一样使用它:

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

it will default to the class names "active" and "active-sub", but you can set custom class names:它将默认为类名“active”和“active-sub”,但您可以设置自定义类名:

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

If you don't need one of the active classes put a space in the string:如果您不需要其中一个活动类,请在字符串中放置一个空格:

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

Just put an a tag in it...只需在其中放置一个标签...

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

This is my solution.这是我的解决方案。 I tokenise the href and the asPath props and then loop through to match them.我标记了hrefasPath道具,然后循环匹配它们。

You can choose an exact link (default)您可以选择一个确切的链接(默认)

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

Or a fuzzy link (matches /events) with the fuzzy prop或者带有fuzzy道具的模糊链接(匹配/事件)

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

Here's the component这是组件

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;

Here is another version of ActiveLink with NextJS (see the result image below)这是另一个带有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);

Call it anywhere随处调用

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

Result :结果

在此处输入图片说明

//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

// in another file // 在另一个文件中

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

add the style file and import it (Globally or in the Active Nav component)添加样式文件并导入(全局或在 Active Nav 组件中)

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

In my case, I've a landing page with the navbar.就我而言,我有一个带有导航栏的登录页面。 Instead of pages, the navbar needs to understand the waypoint in the page like: #home or #aboutMe (scrollspy)导航栏需要了解页面中的航点而不是页面,例如:#home 或 #aboutMe (scrollspy)

I create a component in typescript我在 typescript 中创建了一个组件

import { UrlObject } from "url";


interface ActiveLinkProps {
  activeClassName?: string;
  href: string | UrlObject;
}

// children is the <a>, prop is the "href"
const ActiveLink: React.FC<ActiveLinkProps> = ({ children, ...props }) => {
  const router = useRouter();
  // this will make sure i m passing only one child so i will access the its props easily
  const child = Children.only(children) as React.ReactElement;
  let className = child.props ? child.props.className : "";

  if (router.asPath === props.href && props.activeClassName) {
    className = `${className} ${props.activeClassName}`;
  }

  delete props.activeClassName;

  return (
    <Link href={props.href}>{React.cloneElement(child, { className })}</Link>
  );
};

then use it like this然后像这样使用它

<ActiveLink activeClassName="active" href={href}>
  <a className={`nav-link port-navbar-link ${className}`}>{title}</a>
</ActiveLink>

For those who are using Bootstrap this is the simplest solution对于那些使用 Bootstrap 的人来说,这是最简单的解决方案

<Link href="/">
   <a className={`nav-link ${router.pathname == "/" ? "active" : ""}`}>
      Dashboard
   </a>
</Link>

This worked for me这对我有用

import { useRouter } from "next/router";

... ...

const router = useRouter();
const path = router.asPath.split("?")[0]; // (remove query string and use asPath since dynamic slug was rendering as "[slug]" using pathname)

... ...

 const navigationList = (
    <MenuList
    >
      {links.map((item) => {
        const isActive = path == item.href;

        return (
          <MenuItem
            key={item.id}
            disabled={isActive}
            sx={{
              m: 0,
              p: 0,
              ...(isActive && {
                borderBottom: `1px solid ${theme.palette.primary.light}`,
                backgroundColor: theme.palette.primary.dark,
              }),
            }}
          >
            <StyledLink href={item.href}>{item.label}</StyledLink>
          </MenuItem>
        );
      })}
    </MenuList>
  );

Just found this Tutorial from Vercel's team刚从Vercel 的团队找到这个教程

If you're using Nextjs13, we can do it like this now!如果您使用的是 Nextjs13,我们现在就可以这样做! Doc 文档

import { useSelectedLayoutSegment } from 'next/navigation';

export default function NavLink({href, children}) {
  const segment = useSelectedLayoutSegment();
  const active = href === `/${segment}`;

  return (
    <Link className={active ? "underline":""} href={href}>
      {children}
    </Link>
  );
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM