实现 Tab 切换面板(动态组件)Demo

实现 Tab 切换面板(动态组件)Demo

在 Vue 中,动态组件<component :is="...">)非常适合实现 Tab 切换面板------根据当前选中的 Tab,动态渲染对应的组件,且组件实例会被复用或销毁 (取决于是否使用 <KeepAlive>)。

下面是一个完整的 Vue 3 单文件组件 Demo,包含 Tab 导航和内容区域,并演示了组件切换、状态保持和 Props 传递。


1. 最终效果预览
  • 顶部显示三个 Tab 标签:首页列表设置
  • 点击 Tab 切换下方内容区域,动态渲染对应组件。
  • 每个 Tab 组件拥有独立的内部状态(如输入框内容),切换回去后状态保留 (使用 <KeepAlive>)。

2. 完整代码(TabPanelDemo.vue
vue 复制代码
<template>
  <div class="tab-demo">
    <!-- Tab 导航栏 -->
    <div class="tab-nav">
      <div
        v-for="tab in tabs"
        :key="tab.name"
        class="tab-item"
        :class="{ active: currentTab === tab.name }"
        @click="currentTab = tab.name"
      >
        {{ tab.label }}
      </div>
    </div>

    <!-- 动态组件内容区(带 KeepAlive 缓存) -->
    <div class="tab-content">
      <KeepAlive>
        <component
          :is="currentComponent"
          :user="currentUser"
          @update="handleUpdate"
        />
      </KeepAlive>
    </div>

    <!-- 显示当前激活的 Tab 名称(调试用) -->
    <p style="margin-top: 16px; color: #888;">
      当前激活 Tab:{{ currentTab }}
    </p>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// ----- 1. 定义 Tab 组件(内联方式) -----
// 实际项目中可单独抽离为独立 .vue 文件
const TabHome = {
  name: 'TabHome',
  template: `
    <div>
      <h3>🏠 首页</h3>
      <p>欢迎来到首页!</p>
      <input v-model="inputValue" placeholder="输入一些内容..." />
      <p>输入内容:{{ inputValue }}</p>
    </div>
  `,
  setup() {
    const inputValue = ref('')
    return { inputValue }
  }
}

const TabList = {
  name: 'TabList',
  template: `
    <div>
      <h3>📋 列表</h3>
      <ul>
        <li v-for="item in items" :key="item.id">{{ item.name }}</li>
      </ul>
      <button @click="addItem">添加项</button>
    </div>
  `,
  setup() {
    const items = ref([
      { id: 1, name: 'Vue.js' },
      { id: 2, name: 'React' },
      { id: 3, name: 'Angular' }
    ])
    const addItem = () => {
      const newId = items.value.length + 1
      items.value.push({ id: newId, name: `新项 ${newId}` })
    }
    return { items, addItem }
  }
}

const TabSettings = {
  name: 'TabSettings',
  props: ['user'],
  template: `
    <div>
      <h3>⚙️ 设置</h3>
      <p>当前用户:{{ user?.name || '未登录' }}</p>
      <label><input type="checkbox" v-model="darkMode" /> 启用暗黑模式</label>
      <p>暗黑模式:{{ darkMode ? '开启' : '关闭' }}</p>
    </div>
  `,
  setup() {
    const darkMode = ref(false)
    return { darkMode }
  }
}

// ----- 2. Tab 配置 -----
const tabs = [
  { name: 'home', label: '首页', component: TabHome },
  { name: 'list', label: '列表', component: TabList },
  { name: 'settings', label: '设置', component: TabSettings }
]

// 当前激活的 Tab 名
const currentTab = ref('home')

// 计算当前要渲染的组件
const currentComponent = computed(() => {
  const found = tabs.find(tab => tab.name === currentTab.value)
  return found ? found.component : null
})

// 模拟父组件传递给 Tab 的 Props
const currentUser = ref({ name: '张三', role: 'admin' })

// 监听子组件事件
const handleUpdate = (payload) => {
  console.log('收到子组件更新事件:', payload)
}
</script>

<style scoped>
.tab-demo {
  max-width: 600px;
  margin: 0 auto;
  font-family: Arial, sans-serif;
}
.tab-nav {
  display: flex;
  gap: 4px;
  border-bottom: 2px solid #e8e8e8;
}
.tab-item {
  padding: 10px 20px;
  cursor: pointer;
  border-radius: 6px 6px 0 0;
  transition: background 0.2s;
  user-select: none;
}
.tab-item:hover {
  background: #f0f0f0;
}
.tab-item.active {
  background: #1890ff;
  color: white;
  font-weight: bold;
}
.tab-content {
  padding: 20px;
  border: 1px solid #e8e8e8;
  border-top: none;
  border-radius: 0 0 6px 6px;
  min-height: 200px;
}
</style>

3. 关键点解析
核心概念 代码实现 说明
动态组件 <component :is="currentComponent" /> currentComponent 是一个组件对象或注册名,Vue 会根据它的变化动态渲染不同组件。
Tab 切换 点击 .tab-item 更新 currentTab,再通过 computed 计算 currentComponent 使用 v-for 遍历 tabs 数组,维护 currentTab 响应式状态。
组件状态保持 使用 <KeepAlive> 包裹 <component> 不加 <KeepAlive> 时,每次切换都会销毁 旧组件并创建新组件,内部状态(如输入框内容)会丢失。加上后,组件实例被缓存,状态得以保留。
Props 传递 :user="currentUser" 绑定到动态组件。 只要 currentComponent 对应的组件声明了 props,就会正常接收。
自定义事件 @update="handleUpdate" 子组件可通过 emit('update', data) 向父组件通信,适用于任何动态组件。
内联组件定义 使用 defineComponent(或直接对象)在父文件中定义。 实际项目推荐将 Tab 组件抽离为独立 .vue 文件,便于维护。

4. 运行效果说明
  • 点击 首页 → 显示输入框,输入内容后切换到其他 Tab,再切回首页,输入内容依然存在(KeepAlive 生效)。
  • 点击 列表 → 显示列表和"添加项"按钮,点击添加新项,切换后列表状态保留。
  • 点击 设置 → 显示当前用户(从父组件传递)和暗黑模式开关,状态独立。

5. 与路由的区别
  • 动态组件 :适合同一页面内的局部切换(如设置面板、仪表盘)。
  • Vue Router :适合不同页面的切换,且拥有独立的 URL 和生命周期钩子。

7. 注意事项
  • 如果 Tab 组件之间需要独立的缓存策略 ,可以给 <KeepAlive> 添加 include / exclude 属性。
  • 动态组件的 :is 可以传组件对象 (如 TabHome)或组件名 (字符串),但在 <script setup> 中,使用对象更直接。
  • currentComponentnull 时,<component> 不渲染任何内容,可用于占位或错误处理。