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





Во-первых, вам нужен компонент 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, чтобы ссылка выглядела активированной.
Ссылка: здесь
@larryburns ваша папка с компонентами, imho нет, если ясно, на какую ссылку вы указываете, но вы также можете переименовать свой компонент ссылки на другое имя. Это зависит от вас, вы можете переопределить его в индексном файле или просто переименовать компонент, чтобы он полностью отличался от компонента next / link.
Но это не работает, если вы перенаправляете на вложенные ссылки, такие как www.example.com/blog/xyz. Ссылка становится неактивной во вложенных ссылках.
См. мой ответ для решения, основанного на этом коде, но заботящегося о вложенных ссылках и параметрах URL.
Но это не работает для вложенного маршрута. например он работает для «/ settings», но не работает для «/ settings / locations». Как мы можем включить один и тот же текст в качестве активного для нескольких маршрутов?
Еще одна минимальная версия, поддерживающая опору 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
router.asPath работал только в dev (не знаю почему), я заменил на router.pathname === href || router.pathname === as, чтобы работать в продакшене.
Простое решение на основе хука 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">. Как сделать эту ссылку активной?
Как это сделать, если у вас есть компонент класса?
Но это не работает, если вы перенаправляете на вложенные ссылки, такие как www.example.com/blog/xyz. Ссылка становится неактивной во вложенных ссылках
@gurupalsingh Думаю, в этом случае вы могли бы использовать router.pathname.startsWith("/about") ? "active" : ""
Это решение не сработает, если на странице присутствует параметр!
@ think7teen Вы можете использовать расколоть для выбора части перед параметрами: router.pathname.split("?")[0] == "/about" ? "active"
Идеально. У меня это сработало ... ура, спасибо @Rotareti
Если вы хотите использовать якорную ссылку, попробуйте эту версию кода @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>
Это мое решение. Я токенизирую реквизиты 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
}
Где мне создать файл ссылки? В папке с моими компонентами или в корневой папке? Разве это не плохая практика - иметь два файла с именем "Link"? У вас есть один из следующего / ссылка, а затем компонент с именем ссылка.