我们要实现的案例如上所示
假如我们有这样一个数据结构,注意观察这个数据结构中的 type 字段,它有 2 种值,分别是 section 和 index
section 表示一个章节index 表示一个索引01export const router = [02{ type: 'section', label: '基础入门' },03{ type: 'index', label: '简介' },04{ type: 'index', label: '快速开始' },05{ type: 'index', label: '环境安装' },06{ type: 'index', label: '基本配置' },07{ type: 'section', label: '核心概念' },08{ type: 'index', label: '生命周期' },09{ type: 'index', label: '数据流' },10{ type: 'index', label: '事件处理' },11{ type: 'index', label: '样式管理' },12{ type: 'section', label: '通用组件' },13{ type: 'index', label: 'Button 按钮' },14{ type: 'index', label: 'Input 输入框' },15{ type: 'index', label: 'Table 表格' },16{ type: 'index', label: 'Modal 弹窗' },17{ type: 'section', label: '其他资源' },18{ type: 'index', label: '常见问题' },19{ type: 'index', label: '更新日志' },20{ type: 'index', label: '设计规范' },21{ type: 'index', label: '联系我们' },22];
我们需要实现要给侧边栏的菜单标记编号,但是类型为 section 的菜单没有编号,那么此时,我们就不能简单使用 map(item, index) 中的 index 来完成了
我们可以通过 useRef 来实现这个功能,刚开始的时候定义一个初始值为 0 的变量,在 map 循环中,当遇到类型为 index 的菜单时,将变量自增 1,然后通过 useRef 的 .current 属性来获取当前的值,然后将其作为编号显示在菜单中
完整的代码实现如下所示
001import { useState, useRef } from 'react';002import { router } from './router';003import Main from './main';004005export default function ResponsiveDocs() {006const [currentIndex, setCurrentIndex] = useState(0);007const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);008const sectionIndex = useRef(0);009010function menuClickHandler(index: number) {011setCurrentIndex(index);012setIsMobileMenuOpen(false);013}014015// 进入到 JSX 逻辑之前重置 sectionIndex 的值016if (sectionIndex.current !== 0) {017sectionIndex.current = 0;018}019020return (021<div className="flex w-full bg-white dark:bg-black text-slate-900 dark:text-neutral-200 font-sans overflow-hidden transition-colors duration-300">022023{/* 仅在移动端且菜单打开时显示 */}024{isMobileMenuOpen && (025<div026className="fixed inset-0 bg-black/50 z-4 md:hidden backdrop-blur-sm"027onClick={() => setIsMobileMenuOpen(false)}028/>029)}030031{/* 移动端逻辑: fixed定位 + transform位移实现抽屉效果,桌面端逻辑 (md:): relative定位 + 始终显示 (translate-x-0) */}032<aside033className={`034fixed inset-y-0 left-0 z-5 w-64 bg-slate-50 dark:bg-neutral-900 border-r border-slate-200 dark:border-neutral-800035transform transition-transform duration-300 ease-in-out036${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}037md:relative md:translate-x-0038`}039>040{/* Sidebar Header */}041<div className="h-16 flex items-center justify-between px-6 border-b border-slate-200 dark:border-neutral-800">042<div className="flex items-center gap-2 font-bold tracking-tight text-lg">043<div className="w-3 h-3 bg-blue-600 dark:bg-blue-500"></div>044<span>DOCS</span>045</div>046{/* 移动端关闭按钮 */}047<button onClick={() => setIsMobileMenuOpen(false)} className="md:hidden text-slate-500 dark:text-neutral-400 p-1">✕</button>048</div>049050{/* Menu List */}051<div className="flex-1 p-4">052<div className="space-y-0.5">053{router.map((item, i) => {054if (item.type === 'section') {055return (056<div key={i} className="mt-6 mb-2 px-3">057<span className="text-[10px] uppercase tracking-widest font-bold text-slate-400 dark:text-neutral-600">058{item.label}059</span>060</div>061);062}063064// Index 类型处理065sectionIndex.current++;066const numberStr = String(sectionIndex.current).padStart(2, '0');067const isActive = currentIndex === i;068069return (070<div key={i}>071<button072onClick={() => {menuClickHandler(i);}}073className={`074group w-full flex items-center px-3 py-2 text-sm transition-all075${isActive076? 'bg-white dark:bg-neutral-800 text-blue-600 dark:text-white font-medium'077: 'text-slate-600 dark:text-neutral-400 hover:bg-slate-200/50 dark:hover:bg-neutral-800/50'078}079`}080>081<span className={`082font-mono text-xs mr-3 opacity-60083${isActive ? 'text-blue-500 dark:text-blue-400 opacity-100' : ''}084`}>085{numberStr}086</span>087{item.label}088</button>089</div>090);091})}092</div>093</div>094</aside>095096097<Main activeLabel={router[currentIndex].label} setIsMobileMenuOpen={setIsMobileMenuOpen} />098</div>099);100}
当然,我们还有更简单的方法,因为使用 useRef 会缓存之前的值,因此,当组件第二次渲染时,其数值会从上一轮渲染时的值继续累加,因此,我们必须在组件渲染之前,将 sectionIndex 的值重置为 0
更简单的方法就是直接定义一个普通的变量,这样,它就会在组件 re-render 之前,自动重置为 0
1let sectionIndex = 0;