Target Active Link, когда маршрут активен в Next.js

Как настроить таргетинг на активную ссылку в Next.js, как мы это делаем в React-Router-4? Имеется в виду, присвоить активной ссылке класс, когда ее маршрут активен?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
42
0
43 576
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Во-первых, вам нужен компонент 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

Затем у вас есть панель навигации с созданным компонентом 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

После этого вы можете реализовать панель навигации на своей странице:

import Nav from '../components/Nav'

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

Ключ к тому, как это работает, находится внутри компонента Link, мы сравниваем значение router.pathname с атрибутом href из Link, если значение совпадает с другим, то помещаем конкретное className, чтобы ссылка выглядела активированной.

Ссылка: здесь

Где мне создать файл ссылки? В папке с моими компонентами или в корневой папке? Разве это не плохая практика - иметь два файла с именем "Link"? У вас есть один из следующего / ссылка, а затем компонент с именем ссылка.

larry burns 15.07.2019 10:50

@larryburns ваша папка с компонентами, imho нет, если ясно, на какую ссылку вы указываете, но вы также можете переименовать свой компонент ссылки на другое имя. Это зависит от вас, вы можете переопределить его в индексном файле или просто переименовать компонент, чтобы он полностью отличался от компонента next / link.

Darryl RN 16.07.2019 17:10

Но это не работает, если вы перенаправляете на вложенные ссылки, такие как www.example.com/blog/xyz. Ссылка становится неактивной во вложенных ссылках.

gurupal singh 29.07.2020 04:59

См. мой ответ для решения, основанного на этом коде, но заботящегося о вложенных ссылках и параметрах URL.

Felix 05.04.2021 10:51

Но это не работает для вложенного маршрута. например он работает для «/ settings», но не работает для «/ settings / locations». Как мы можем включить один и тот же текст в качестве активного для нескольких маршрутов?

Deep Kakkar 21.10.2021 13:30

Еще одна минимальная версия, поддерживающая опору 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>
));

Мне нравится этот ответ, если вы хотите сохранить классы от детей и просто добавить их, используйте: className: router.asPath === href || router.asPath === как? ${children.props.className} active: children.props.className

Goran Jakovljevic 11.01.2019 11:10
router.asPath работал только в dev (не знаю почему), я заменил на router.pathname === href || router.pathname === as, чтобы работать в продакшене.
viery365 22.03.2019 18:19

Простое решение на основе хука 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>
  );
};

Что, если ваша ссылка ведет на якорь вроде <Link href = "/#about">. Как сделать эту ссылку активной?

Isaac Pak 03.05.2020 22:03

Как это сделать, если у вас есть компонент класса?

user2570135 17.07.2020 04:37

Но это не работает, если вы перенаправляете на вложенные ссылки, такие как www.example.com/blog/xyz. Ссылка становится неактивной во вложенных ссылках

gurupal singh 29.07.2020 05:00

@gurupalsingh Думаю, в этом случае вы могли бы использовать router.pathname.startsWith("/about") ? "active" : ""

Rotareti 29.07.2020 11:06

Это решение не сработает, если на странице присутствует параметр!

seem7teen 18.09.2020 14:43

@ think7teen Вы можете использовать расколоть для выбора части перед параметрами: router.pathname.split("?")[0] == "/about" ? "active"

Rotareti 19.09.2020 14:26

Идеально. У меня это сработало ... ура, спасибо @Rotareti

Ibad Shaikh 21.02.2021 14:03

Если вы хотите использовать якорную ссылку, попробуйте эту версию кода @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>
  );
}`;

Версия машинописного текста:

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

Обратите внимание, что я не клонирую ребенка без необходимости.

Вот решение, которое также работает при наличии URL-параметров и проверяет, активна ли подстраница. На основе ответов Дэррила Р.Н. и Самана Мохамади.

Он работает как прямая замена для компонента ссылки NextJS и добавляет классы «активный» и «активный-подпункт», если активен маршрут или маршрут подстраницы.

Создайте файл с именем 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" (следующий / ссылка):

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

Просто поставьте в него метку ...

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

OP не упомянул Tailwind или какой-либо CSS-фреймворк, а относительно active сказал "присвоить активной ссылке класс, когда ее маршрут активен". Это не то, что делает псевдокласс CSS active.

ChrisCrossCrash 17.11.2021 09:43

Это мое решение. Я токенизирую реквизиты href и asPath, а затем просматриваю их, чтобы сопоставить их.

Вы можете выбрать точную ссылку (по умолчанию)

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

Вот еще одна версия ActiveLink с NextJS (см. Изображение результата ниже)

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>

Результат:

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

add the style file and import it (Globally or in the Active Nav component)

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

Другие вопросы по теме