Next.js+React+Tailwind项目:页面切换时高亮导航项的实现方法
问题描述
我英语不太流利,这是我第一次提问,也是我的第一个Next.js项目。
项目依赖
"dependencies": { "@headlessui/react": "^1.7.15", "@heroicons/react": "^2.0.18", "autoprefixer": "10.4.14", "next": "13.4.7", "postcss": "8.4.24", "react": "18.2.0", "react-dom": "18.2.0", "tailwindcss": "3.3.2" }
项目结构

现有代码
Navbar.js
'use client'; import { Fragment } from 'react'; import { Disclosure, Menu, Transition } from '@headlessui/react'; import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/react/24/outline'; const navigation = [ { name: 'Dashboard', href: '#', current: true }, { name: 'Team', href: '#', current: false }, { name: 'Projects', href: '#', current: false }, { name: 'Calendar', href: '#', current: false }, ]; function classNames(...classes) { return classes.filter(Boolean).join(' '); } export default function Example() { return ( <Disclosure as='nav' className='bg-white shadow-md'> {({ open }) => ( <> <div className='mx-auto w-full px-2 sm:px-6 lg:px-8'> <div className='relative flex h-16 items-center justify-between'> <div className='absolute inset-y-0 left-0 flex items-center sm:hidden'> {/* Mobile menu button*/} <Disclosure.Button className='inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white'> <span className='sr-only'>Open main menu</span> {open ? ( <XMarkIcon className='block h-6 w-6' aria-hidden='true' /> ) : ( <Bars3Icon className='block h-6 w-6' aria-hidden='true' /> )} </Disclosure.Button> </div> <div className='flex flex-1 items-center justify-center sm:items-stretch sm:justify-start'> <div className='flex flex-shrink-0 items-center'> <img className='block h-8 w-auto lg:hidden' src='https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500' alt='Your Company' /> <img className='hidden h-8 w-auto lg:block' src='https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500' alt='Your Company' /> </div> <div className='hidden sm:ml-6 sm:block'> <div className='flex space-x-4'> {navigation.map((item) => ( <a key={item.name} href={item.href} className={classNames( item.current ? 'text-orange-500 border-b-2 border-orange-500' : 'text-gray-300 hover:border-b-2 hover:border-orange-300 hover:text-orange-300', 'rounded-sm px-3 py-5 text-sm font-medium' )} aria-current={item.current ? 'page' : undefined} > {item.name} </a> ))} </div> </div> </div> <div className='absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0'> {/* Profile dropdown */} <Menu as='div' className='relative ml-3'> <div> <Menu.Button className='flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800'> <span className='sr-only'>Open user menu</span> <img className='h-8 w-8 rounded-full' src='/images/me.jpeg' alt='photo' /> </Menu.Button> </div> <Transition as={Fragment} enter='transition ease-out duration-100' enterFrom='transform opacity-0 scale-95' enterTo='transform opacity-100 scale-100' leave='transition ease-in duration-75' leaveFrom='transform opacity-100 scale-100' leaveTo='transform opacity-0 scale-95' > <Menu.Items className='absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none'> <Menu.Item> {({ active }) => ( <a href='#' className={classNames( active ? 'bg-orange-500' : '', 'block px-4 py-2 text-sm text-black sm:text-white ' )} > Hi ! Nice to meet you! </a> )} </Menu.Item> </Menu.Items> </Transition> </Menu> </div> </div> </div> <Disclosure.Panel className='sm:hidden'> <div className='space-y-1 px-2 pb-3 pt-2'> {navigation.map((item) => ( <Disclosure.Button key={item.name} as='a' href={item.href} className={classNames( item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white', 'block rounded-md px-3 py-2 text-base font-medium' )} aria-current={item.current ? 'page' : undefined} > {item.name} </Disclosure.Button> ))} </div> </Disclosure.Panel> </> )} </Disclosure> ); }
layout.js
import Navbar from '../components/Navbar'; export default function DashboardLayout({ children, }) { return ( <section> <Navbar /> {children} </section> ); }
我希望点击页面跳转时,导航栏的当前项能高亮显示。尝试修改navItem的current属性状态,但不知道如何实现,也不清楚如何判断当前所在页面。
解决方案
要实现导航栏当前项高亮,需要结合Next.js的路由状态动态判断页面,以下是具体步骤:
1. 替换跳转标签为Next.js的<Link>组件
用Next.js客户端路由组件<Link>替代原生<a>,避免页面刷新,同时更好跟踪路由状态:
// 在Navbar.js顶部导入Link import Link from 'next/link';
2. 跟踪当前路由路径
使用Next.js的usePathname钩子获取当前页面路径:
// 在Navbar.js顶部导入usePathname import { usePathname } from 'next/navigation'; // 在组件内部获取路径 export default function Navbar() { const pathname = usePathname(); // ...其余代码 }
3. 配置实际路由路径
修改navigation数组的href为实际页面路径(需确保项目中存在对应页面文件,比如app/dashboard/page.js):
const navigation = [ { name: 'Dashboard', href: '/dashboard' }, { name: 'Team', href: '/team' }, { name: 'Projects', href: '/projects' }, { name: 'Calendar', href: '/calendar' }, ];
4. 动态判断并设置高亮类名
对比当前路径和导航项路径,动态生成高亮样式:
桌面端导航修改
{navigation.map((item) => { const isCurrent = pathname === item.href; return ( <Link key={item.name} href={item.href} className={classNames( isCurrent ? 'text-orange-500 border-b-2 border-orange-500' : 'text-gray-300 hover:border-b-2 hover:border-orange-300 hover:text-orange-300', 'rounded-sm px-3 py-5 text-sm font-medium' )} aria-current={isCurrent ? 'page' : undefined} > {item.name} </Link> ); })}
移动端导航修改
{navigation.map((item) => { const isCurrent = pathname === item.href; return ( <Disclosure.Button key={item.name} as={Link} href={item.href} className={classNames( isCurrent ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white', 'block rounded-md px-3 py-2 text-base font-medium' )} aria-current={isCurrent ? 'page' : undefined} > {item.name} </Disclosure.Button> ); })}
完整修改后的Navbar.js
'use client'; import { Fragment } from 'react'; import { Disclosure, Menu, Transition } from '@headlessui/react'; import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; const navigation = [ { name: 'Dashboard', href: '/dashboard' }, { name: 'Team', href: '/team' }, { name: 'Projects', href: '/projects' }, { name: 'Calendar', href: '/calendar' }, ]; function classNames(...classes) { return classes.filter(Boolean).join(' '); } export default function Navbar() { const pathname = usePathname(); return ( <Disclosure as='nav' className='bg-white shadow-md'> {({ open }) => ( <> <div className='mx-auto w-full px-2 sm:px-6 lg:px-8'> <div className='relative flex h-16 items-center justify-between'> <div className='absolute inset-y-0 left-0 flex items-center sm:hidden'> {/* Mobile menu button*/} <Disclosure.Button className='inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white'> <span className='sr-only'>Open main menu</span> {open ? ( <XMarkIcon className='block h-6 w-6' aria-hidden='true' /> ) : ( <Bars3Icon className='block h-6 w-6' aria-hidden='true' /> )} </Disclosure.Button> </div> <div className='flex flex-1 items-center justify-center sm:items-stretch sm:justify-start'> <div className='flex flex-shrink-0 items-center'> <img className='block h-8 w-auto lg:hidden' src='https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500' alt='Your Company' /> <img className='hidden h-8 w-auto lg:block' src='https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500' alt='Your Company' /> </div> <div className='hidden sm:ml-6 sm:block'> <div className='flex space-x-4'> {navigation.map((item) => { const isCurrent = pathname === item.href; return ( <Link key={item.name} href={item.href} className={classNames( isCurrent ? 'text-orange-500 border-b-2 border-orange-500' : 'text-gray-300 hover:border-b-2 hover:border-orange-300 hover:text-orange-300', 'rounded-sm px-3 py-5 text-sm font-medium' )} aria-current={isCurrent ? 'page' : undefined} > {item.name} </Link> ); })} </div> </div> </div> <div className='absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0'> {/* Profile dropdown */} <Menu as='div' className='relative ml-3'> <div> <Menu.Button className='flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800'> <span className='sr-only'>Open user menu</span> <img className='h-8 w-8 rounded-full' src='/images/me.jpeg' alt='photo' /> </Menu.Button> </div> <Transition as={Fragment} enter='transition ease-out duration-100' enterFrom='transform opacity-0 scale-95' enterTo='transform opacity-100 scale-100' leave='transition ease-in duration-75' leaveFrom='transform opacity-100 scale-100' leaveTo='transform opacity-0 scale-95' > <Menu.Items className='absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none'> <Menu.Item> {({ active }) => ( <Link href='#' className={classNames( active ? 'bg-orange-500' : '', 'block px-4 py-2 text-sm text-black sm:text-white ' )} > Hi ! Nice to meet you! </Link> )} </Menu.Item> </Menu.Items> </Transition> </Menu> </div> </div> </div> <Disclosure.Panel className='sm:hidden'> <div className='space-y-1 px-2 pb-3 pt-2'> {navigation.map((item) => { const isCurrent = pathname === item.href; return ( <Disclosure.Button key={item.name} as={Link} href={item.href} className={classNames( isCurrent ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white', 'block rounded-md px-3 py-2 text-base font-medium' )} aria-current={isCurrent ? 'page' : undefined} > {item.name} </Disclosure.Button> ); })} </div> </Disclosure.Panel> </> )} </Disclosure> ); }
注意事项
- 若需匹配子路由(如
/dashboard/settings),可将isCurrent判断改为pathname.startsWith(item.href)。 - 确保项目中已创建对应路径的页面文件,否则路由跳转无效。
内容的提问来源于stack exchange,提问作者Felipe Betini
相关产品推荐
相关产品推荐

