You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Vue.js 3项目中操作DOM元素:应选用原生JS、jQuery还是纯Vue.js方案?

绝对应该坚持纯Vue.js方案,别碰框架外的DOM操作

先给你拍板:不要用querySelector或jQuery来处理这些状态逻辑,从务实的角度看,框架外的DOM操作会给你带来一堆后续难以解决的问题,而Vue本身完全有能力优雅搞定递归侧边栏的状态管理。我给你拆解下原因和具体解决方案:

为什么不能用框架外的DOM操作?

  • 违背Vue的核心原理:数据驱动DOM
    Vue的DOM是完全由数据状态驱动的,如果你手动用querySelector去加/删active/showing类,相当于绕开了Vue的响应式系统。举个例子:你手动给某个链接加了active类,但Vue的状态里没记录这个变化,当组件因为路由跳转、父组件更新等原因重新渲染时,Vue会根据当前数据状态重新生成DOM,你手动加的类会被覆盖,出现“状态跳变”的诡异bug。
  • 维护性灾难
    递归组件的结构本身就有嵌套层级,用jQuery/querySelector的话,你得写一堆复杂的选择器(比如$('.sidebar-item').siblings().removeClass('active'))来定位元素。一旦后续你调整了组件的DOM结构(比如给外层加了个容器、改了类名),所有这些DOM操作的代码都要跟着改,而Vue的方案只需要维护数据逻辑,DOM会自动同步。
  • 状态同步成本极高
    比如你要实现“激活一个链接时其他链接自动取消激活”,用DOM操作的话,每次点击都要遍历所有链接元素,移除active类再给当前元素加上。但如果是路由跳转触发的激活(比如用户直接输入URL),你还要额外监听路由变化再重复一遍这个操作。而Vue的方案只需要让所有组件共享“当前激活路由”这个状态,自动判断是否加类,完全不需要手动同步。

务实的Vue.js解决方案

针对递归侧边栏的状态管理,核心思路是把所有状态(激活、展开/折叠)放到Vue的响应式系统里,让组件根据状态自动渲染,而不是手动操作DOM。这里给你一套具体的实现方案:

1. 用状态管理工具统一管理菜单状态

如果你的项目已经用了Pinia(Vue3推荐)或Vuex,直接用它们来存全局的菜单状态:

  • 当前激活的路由路径(或者直接用Vue Router的$route对象)
  • 每个菜单的展开/折叠状态(用一个对象存储,键是菜单的路由path,值是布尔值)

举个Pinia的例子:

// stores/menu.js
import { defineStore } from 'pinia'

export const useMenuStore = defineStore('menu', {
  state: () => ({
    // 存储展开的菜单,key是路由path
    expandedMenus: {}
  }),
  actions: {
    // 切换单个菜单的展开状态
    toggleExpand(path) {
      this.expandedMenus[path] = !this.expandedMenus[path]
    },
    // 路由跳转时自动展开所有父级菜单
    expandParentMenus(currentPath) {
      const pathParts = currentPath.split('/')
      for (let i = 1; i < pathParts.length; i++) {
        const parentPath = pathParts.slice(0, i + 1).join('/')
        this.expandedMenus[parentPath] = true
      }
    }
  }
})

2. 写递归的侧边栏子组件

每个子组件只需要接收菜单数据,然后根据全局状态判断自己的样式:

<!-- SidebarItem.vue -->
<template>
  <div class="sidebar-item">
    <!-- 菜单链接 -->
    <a
      :class="{ 
        active: isActive, 
        collapsed: !isExpanded && menuItem.children 
      }"
      @click.prevent="handleMenuItemClick"
    >
      {{ menuItem.label }}
      <!-- 展开/折叠图标 -->
      <span v-if="menuItem.children" class="toggle-icon">
        {{ isExpanded ? '▼' : '▶' }}
      </span>
    </a>

    <!-- 递归渲染子菜单 -->
    <div 
      v-if="menuItem.children && isExpanded" 
      class="sidebar-submenu"
    >
      <SidebarItem
        v-for="child in menuItem.children"
        :key="child.path"
        :menu-item="child"
      />
    </div>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useMenuStore } from '@/stores/menu'

const props = defineProps({
  menuItem: {
    type: Object,
    required: true
  }
})

const route = useRoute()
const menuStore = useMenuStore()

// 判断当前菜单是否处于激活状态(支持嵌套路由)
const isActive = computed(() => {
  return route.matched.some(route => route.path.startsWith(props.menuItem.path))
})

// 判断当前菜单是否展开
const isExpanded = computed(() => {
  return menuStore.expandedMenus[props.menuItem.path] || false
})

const handleMenuItemClick = () => {
  // 如果有子菜单,切换展开状态
  if (props.menuItem.children) {
    menuStore.toggleExpand(props.menuItem.path)
  } 
  // 没有子菜单的话,直接跳转路由
  else {
    route.push(props.menuItem.path)
  }
}
</script>

3. 路由跳转时自动展开父菜单

在Vue Router的后置守卫里调用状态管理的方法,确保用户跳转路由时,对应的父菜单自动展开:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useMenuStore } from '@/stores/menu'

const router = createRouter({
  history: createWebHistory(),
  routes: [/* 你的路由配置数组 */]
})

router.afterEach((to) => {
  const menuStore = useMenuStore()
  menuStore.expandParentMenus(to.path)
})

export default router

最后总结

用Vue的响应式系统+状态管理来处理递归侧边栏的状态,不仅能完美解决你提到的类切换、状态关联问题,而且代码更简洁、维护性更强,后续扩展功能(比如记住用户的展开状态、权限控制菜单显示)也会非常方便。而框架外的DOM操作只会给你埋下一堆难以排查的bug,完全是捡芝麻丢西瓜的选择。

内容的提问来源于stack exchange,提问作者Giovanni Di Toro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 19:28:15