用 shallowreactive 优化侧边栏菜单,仅代理顶层状态(如 openkeys、selectedkey),跳过深层字段递归响应,配合 markraw、状态分离、局部 reactive 和虚拟滚动,显著提升性能。

用 shallowReactive 优化后台管理侧边栏菜单,核心是避免对菜单数据中大量嵌套字段做无谓的响应式代理——菜单结构通常只在初始化或权限变更时整体替换,展开/收起、高亮选中等操作也基本只改顶层状态(如 openKeys、selectedKey),深层字段(如每个菜单项的 icon、children 描述)几乎不修改。
只代理菜单配置的顶层,跳过所有子菜单递归
后台菜单常以树形结构返回,例如包含上百个节点、多层 children 的数组。若用 reactive(menuData),Vue 会为每个节点、每个子节点、每个图标对象都创建 Proxy 实例并建立依赖链,内存暴涨、首屏挂载慢。改用 shallowReactive 后,仅 menuData 本身、它的 length、直接属性(如 menuData.openKeys)可触发更新,而 menuData.items[0].children[2].title 这类路径修改完全不触发重渲染——这恰恰符合菜单的真实使用模式。
- ✅ 推荐写法:
const menu = shallowReactive(apiResponse.menu) - ❌ 避免写法:
const menu = reactive(apiResponse.menu)(尤其当 items > 50) - 配合
markRaw处理图标组件或第三方对象:shallowReactive({ items: apiResponse.items.map(item => ({ ...item, icon: markRaw(item.icon) })) })
菜单状态与菜单数据分离管理
侧边栏的交互状态(如哪些菜单展开、当前选中项)不应混在原始菜单数据里。把它们抽离成独立的浅层响应式对象,既能精准控制更新范围,又避免污染原始配置。
使用 @ainative/react-sdk 为 React 应用添加 AI 聊天和积分。适用于 (1) 安装 @ainative/react-sdk,(2) 使用 useChat hook 实现聊天完成。
- 定义独立状态:
const sidebarState = shallowReactive({ openKeys: [], selectedKey: '' }) - 模板中用
v-for="item in menu.items"渲染,高亮逻辑写成::class="{ active: sidebarState.selectedKey === item.key }" - 展开收起只改
sidebarState.openKeys,不影响menu本身的任何字段,杜绝意外触发深层依赖更新
深层字段需响应时,按需增强,不一刀切
极少数场景下,你可能需要监听某个子菜单的特定字段(比如动态切换某菜单项的 disabled 状态)。这时不要把整个菜单变回 reactive,而是局部增强:
- 对单个菜单项启用深度响应:
const editableItem = reactive(menu.items.find(i => i.key === 'sys-config')) - 或提前计算为顶层字段:
const isConfigDisabled = computed(() => menu.items.find(i => i.key === 'sys-config')?.disabled) - 如果只是展示用,用
toRaw安全读取原始值,避开 Proxy 访问开销:const rawIcon = toRaw(item.icon)
搭配虚拟滚动处理超长菜单列表
当侧边栏菜单项超过 100 条(比如权限粒度极细的 SaaS 后台),即使用了 shallowReactive,一次性渲染所有 DOM 节点仍会造成卡顿。此时应叠加虚拟滚动技术:
- Vue 生态推荐
vue-virtual-scroller或virtuoso,只渲染可视区域内的菜单项 - React 生态可用
react-window的FixedSizeList,如知识库所示:<fixedsizelist height="{400}" itemcount="{menu.items.length}" itemsize="{48}"></fixedsizelist> - 注意:虚拟滚动容器需包裹在
shallowReactive管理的状态之外,保持其自身渲染逻辑独立
不复杂但容易忽略:菜单数据初始化后,就不再需要深度响应;真正要响应的,只是那几个开关和选中态。把响应式“减”到刚好够用,性能提升立竿见影。










