Table of Contents

1、概述

预览
基础入门

基础入门

我们要实现的案例如上所示

假如我们有这样一个数据结构,注意观察这个数据结构中的 type 字段,它有 2 种值,分别是 sectionindex

  • section 表示一个章节
  • index 表示一个索引
router.ts
01
export 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 属性来获取当前的值,然后将其作为编号显示在菜单中

完整的代码实现如下所示

index.tsx
router.ts
main.tsx
001
import { useState, useRef } from 'react';
002
import { router } from './router';
003
import Main from './main';
004
005
export default function ResponsiveDocs() {
006
const [currentIndex, setCurrentIndex] = useState(0);
007
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
008
const sectionIndex = useRef(0);
009
010
function menuClickHandler(index: number) {
011
setCurrentIndex(index);
012
setIsMobileMenuOpen(false);
013
}
014
015
// 进入到 JSX 逻辑之前重置 sectionIndex 的值
016
if (sectionIndex.current !== 0) {
017
sectionIndex.current = 0;
018
}
019
020
return (
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">
022
023
{/* 仅在移动端且菜单打开时显示 */}
024
{isMobileMenuOpen && (
025
<div
026
className="fixed inset-0 bg-black/50 z-4 md:hidden backdrop-blur-sm"
027
onClick={() => setIsMobileMenuOpen(false)}
028
/>
029
)}
030
031
{/* 移动端逻辑: fixed定位 + transform位移实现抽屉效果,桌面端逻辑 (md:): relative定位 + 始终显示 (translate-x-0) */}
032
<aside
033
className={`
034
fixed inset-y-0 left-0 z-5 w-64 bg-slate-50 dark:bg-neutral-900 border-r border-slate-200 dark:border-neutral-800
035
transform transition-transform duration-300 ease-in-out
036
${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}
037
md:relative md:translate-x-0
038
`}
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>
049
050
{/* Menu List */}
051
<div className="flex-1 p-4">
052
<div className="space-y-0.5">
053
{router.map((item, i) => {
054
if (item.type === 'section') {
055
return (
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
}
063
064
// Index 类型处理
065
sectionIndex.current++;
066
const numberStr = String(sectionIndex.current).padStart(2, '0');
067
const isActive = currentIndex === i;
068
069
return (
070
<div key={i}>
071
<button
072
onClick={() => {menuClickHandler(i);}}
073
className={`
074
group w-full flex items-center px-3 py-2 text-sm transition-all
075
${isActive
076
? '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={`
082
font-mono text-xs mr-3 opacity-60
083
${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>
095
096
097
<Main activeLabel={router[currentIndex].label} setIsMobileMenuOpen={setIsMobileMenuOpen} />
098
</div>
099
);
100
}

当然,我们还有更简单的方法,因为使用 useRef 会缓存之前的值,因此,当组件第二次渲染时,其数值会从上一轮渲染时的值继续累加,因此,我们必须在组件渲染之前,将 sectionIndex 的值重置为 0

更简单的方法就是直接定义一个普通的变量,这样,它就会在组件 re-render 之前,自动重置为 0

index.tsx
1
let sectionIndex = 0;
专栏首页
到顶
专栏目录