动态组件

什么是动态组件?

动态组件是 Vue 中的一个重要特性,允许我们根据条件动态地切换不同的组件。在 Vue3 中,我们使用 <component> 元素配合 :is 属性来实现动态组件。

1. 基本使用

使用 <component :is="componentName"> 来动态渲染组件。

示例:基本动态组件

<template>
  <div>
    <div class="tabs">
      <button 
        v-for="tab in tabs" 
        :key="tab.name"
        @click="currentTab = tab.name"
        :class="{ active: currentTab === tab.name }"
      >
        {{ tab.label }}
      </button>
    </div>
    
    <div class="tab-content">
      <!-- 动态组件 -->
      <component :is="currentTab" />
    </div>
  </div>
</template>

<script>
// 导入组件
import HomeComponent from './HomeComponent.vue'
import AboutComponent from './AboutComponent.vue'
import ContactComponent from './ContactComponent.vue'

export default {
  name: 'DynamicComponentDemo',
  components: {
    HomeComponent,
    AboutComponent,
    ContactComponent
  },
  data() {
    return {
      currentTab: 'HomeComponent',
      tabs: [
        { name: 'HomeComponent', label: '首页' },
        { name: 'AboutComponent', label: '关于我们' },
        { name: 'ContactComponent', label: '联系我们' }
      ]
    }
  }
}
</script>

<style scoped>
.tabs {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.tabs button {
  padding: 10px 20px;
  border: 1px solid #ddd;
  background-color: #f5f5f5;
  cursor: pointer;
  border-radius: 4px;
}

.tabs button.active {
  background-color: #42b883;
  color: white;
  border-color: #42b883;
}

.tab-content {
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 4px;
}
</style>

2. 组件缓存

使用 <KeepAlive> 组件可以缓存动态组件的状态,避免组件被频繁创建和销毁。

示例:缓存动态组件

<template>
  <div>
    <div class="tabs">
      <button 
        v-for="tab in tabs" 
        :key="tab.name"
        @click="currentTab = tab.name"
        :class="{ active: currentTab === tab.name }"
      >
        {{ tab.label }}
      </button>
    </div>
    
    <div class="tab-content">
      <!-- 使用 KeepAlive 缓存组件 -->
      <KeepAlive>
        <component :is="currentTab" />
      </KeepAlive>
    </div>
  </div>
</template>

<script>
// 导入组件
import { KeepAlive } from 'vue'
import HomeComponent from './HomeComponent.vue'
import AboutComponent from './AboutComponent.vue'
import ContactComponent from './ContactComponent.vue'

export default {
  name: 'DynamicComponentDemo',
  components: {
    KeepAlive,
    HomeComponent,
    AboutComponent,
    ContactComponent
  },
  data() {
    return {
      currentTab: 'HomeComponent',
      tabs: [
        { name: 'HomeComponent', label: '首页' },
        { name: 'AboutComponent', label: '关于我们' },
        { name: 'ContactComponent', label: '联系我们' }
      ]
    }
  }
}
</script>

3. KeepAlive 的 include 和 exclude 属性

可以使用 include 和 exclude 属性来控制哪些组件需要被缓存。

示例:条件缓存

<template>
  <div>
    <!-- include:只缓存指定名称的组件 -->
    <KeepAlive include="HomeComponent,AboutComponent">
      <component :is="currentTab" />
    </KeepAlive>
    
    <!-- exclude:不缓存指定名称的组件 -->
    <KeepAlive exclude="ContactComponent">
      <component :is="currentTab" />
    </KeepAlive>
    
    <!-- 使用数组语法 -->
    <KeepAlive :include="['HomeComponent', 'AboutComponent']">
      <component :is="currentTab" />
    </KeepAlive>
  </div>
</template>

4. KeepAlive 的 max 属性

Vue 3.2+ 新增了 max 属性,用于限制缓存组件的最大数量。

示例:限制缓存数量

<template>
  <div>
    <!-- 最多缓存 2 个组件 -->
    <KeepAlive :max="2">
      <component :is="currentTab" />
    </KeepAlive>
  </div>
</template>

5. 动态组件的生命周期

当使用 <KeepAlive> 时,组件会触发额外的生命周期钩子:

  • onActivated:组件被激活时触发
  • onDeactivated:组件被停用时触发

示例:使用激活和停用钩子

<script setup>
import { onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'

onMounted(() => {
  console.log('组件挂载')
})

onUnmounted(() => {
  console.log('组件卸载')
})

onActivated(() => {
  console.log('组件激活')
})

onDeactivated(() => {
  console.log('组件停用')
})
</script>

6. 动态组件的异步加载

可以结合动态导入实现组件的异步加载,提高应用的初始加载性能。

示例:异步动态组件

<template>
  <div>
    <button @click="loadComponent">加载异步组件</button>
    <div v-if="AsyncComponent">
      <component :is="AsyncComponent" />
    </div>
  </div>
</template>

<script>
export default {
  name: 'AsyncComponentDemo',
  data() {
    return {
      AsyncComponent: null
    }
  },
  methods: {
    async loadComponent() {
      // 动态导入组件
      const module = await import('./AsyncComponent.vue')
      this.AsyncComponent = module.default
    }
  }
}
</script>

7. 动态组件的高级用法

可以结合响应式数据和计算属性实现更复杂的动态组件逻辑。

示例:基于计算属性的动态组件

<template>
  <div>
    <select v-model="selectedType">
      <option value="text">文本输入</option>
      <option value="number">数字输入</option>
      <option value="select">下拉选择</option>
    </select>
    
    <div class="form-field">
      <component :is="currentComponent" v-model="fieldValue" />
    </div>
    
    <p>当前值: {{ fieldValue }}</p>
  </div>
</template>

<script>
import TextInput from './TextInput.vue'
import NumberInput from './NumberInput.vue'
import SelectInput from './SelectInput.vue'

export default {
  name: 'DynamicFormDemo',
  components: {
    TextInput,
    NumberInput,
    SelectInput
  },
  data() {
    return {
      selectedType: 'text',
      fieldValue: ''
    }
  },
  computed: {
    currentComponent() {
      // 根据类型返回不同的组件
      const componentMap = {
        text: 'TextInput',
        number: 'NumberInput',
        select: 'SelectInput'
      }
      return componentMap[this.selectedType]
    }
  }
}
</script>

使用场景

  • 标签页(Tabs)切换
  • 动态表单生成
  • 路由视图(Vue Router)
  • 模态框内容动态切换
  • 根据用户权限显示不同组件

最佳实践

  • 合理使用 <KeepAlive> 缓存频繁切换的组件
  • 使用 include 和 exclude 精确控制缓存范围
  • 对于大型组件,考虑使用异步加载
  • 结合计算属性实现复杂的组件切换逻辑
  • 注意 onActivated 和 onDeactivated 钩子的使用