{T}

概述

Vue 2 提供了一系列内置组件,这些组件无需注册即可在任何组件模板中直接使用。内置组件主要用于实现动态组件、过渡动画、组件缓存和内容分发等核心功能。

内置组件列表

组件用途是否渲染真实 DOM
component动态组件渲染
transition单元素/组件过渡动画否(抽象组件)
transition-group多元素/组件过渡动画
keep-alive组件实例缓存否(抽象组件)
slot内容分发插槽否(插槽占位符)

component

功能描述

<component> 是一个动态组件容器,根据 is 属性的值动态渲染不同的组件。它允许在运行时决定渲染哪个组件,是实现组件动态切换的核心方式。

Props

属性类型必填说明
isstring | ComponentDefinition | ComponentConstructor要渲染的组件名称或组件定义对象
inline-templateboolean是否将组件内容作为内联模板

基础用法

html
<div id="app">
  <button @click="currentComponent = 'Home'">首页</button>
  <button @click="currentComponent = 'About'">关于</button>
  <button @click="currentComponent = 'Contact'">联系</button>

  <component :is="currentComponent"></component>
</div>
javascript
new Vue({
  el: "#app",
  data: {
    currentComponent: "Home"
  },
  components: {
    Home: {
      template: "<div>首页内容</div>"
    },
    About: {
      template: "<div>关于我们</div>"
    },
    Contact: {
      template: "<div>联系方式</div>"
    }
  }
})

高级用法

使用组件对象引用

html
<component :is="$options.components.child"></component>

结合计算属性动态选择组件

html
<template>
  <div>
    <input v-model="viewType" type="radio" value="list" /> 列表视图
    <input v-model="viewType" type="radio" value="grid" /> 网格视图

    <component :is="currentView" :items="items"></component>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        viewType: "list",
        items: [1, 2, 3, 4, 5]
      }
    },
    computed: {
      currentView() {
        return this.viewType === "list" ? "ListView" : "GridView"
      }
    },
    components: {
      ListView: {
        props: ["items"],
        template: '<ul><li v-for="item in items" :key="item">{{ item }}</li></ul>'
      },
      GridView: {
        props: ["items"],
        template:
          '<div class="grid"><span v-for="item in items" :key="item">{{ item }}</span></div>'
      }
    }
  }
</script>

注意事项

  • 动态组件切换时,默认会销毁旧组件并创建新组件,状态不会保留
  • 如需保留状态,请配合 <keep-alive> 使用
  • is 属性可以是已注册的组件名、组件选项对象或组件构造函数

transition

功能描述

<transition> 为单个元素或组件提供进入/离开过渡动画效果。它是一个抽象组件,不会渲染真实的 DOM 元素,只负责管理过渡动画的生命周期。

Props

属性类型默认值说明
namestring'v'自动生成 CSS 过渡类名的前缀
appearbooleanfalse是否在初始渲染时应用过渡
cssbooleantrue是否使用 CSS 过渡类
typestring-指定过渡结束事件类型:'transition''animation'
modestring-控制过渡时序:'out-in''in-out'
durationnumber | object-显式指定过渡持续时间
enter-classstring-自定义进入起始状态类名
enter-active-classstring-自定义进入过渡类名
enter-to-classstring-自定义进入结束状态类名
leave-classstring-自定义离开起始状态类名
leave-active-classstring-自定义离开过渡类名
leave-to-classstring-自定义离开结束状态类名
appear-classstring-自定义初始渲染起始状态类名
appear-active-classstring-自定义初始渲染过渡类名
appear-to-classstring-自定义初始渲染结束状态类名

事件

事件名参数说明
before-enterel进入过渡开始前触发
enterel, done进入过渡开始时触发
after-enterel进入过渡结束后触发
enter-cancelledel进入过渡被取消时触发
before-leaveel离开过渡开始前触发
leaveel, done离开过渡开始时触发
after-leaveel离开过渡结束后触发
leave-cancelledel离开过渡被取消时触发(仅 v-show
before-appearel初始渲染过渡开始前触发
appearel, done初始渲染过渡开始时触发
after-appearel初始渲染过渡结束后触发
appear-cancelledel初始渲染过渡被取消时触发

CSS 过渡类名

过渡过程中,Vue 会自动添加/移除以下 CSS 类名(以 name="fade" 为例):

code
fade-enter        → 进入起始状态
fade-enter-active → 进入过渡状态
fade-enter-to     → 进入结束状态
fade-leave        → 离开起始状态
fade-leave-active → 离开过渡状态
fade-leave-to     → 离开结束状态

基础用法

html
<div id="app">
  <button @click="show = !show">切换</button>

  <transition name="fade">
    <p v-if="show">Hello, Vue!</p>
  </transition>
</div>
css
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
javascript
new Vue({
  el: "#app",
  data: {
    show: true
  }
})

过渡模式

当同时有元素进入和离开时,可以使用 mode 属性控制时序:

html
<transition name="fade" mode="out-in">
  <button v-if="isEditing" key="save" @click="isEditing = false">保存</button>
  <button v-else key="edit" @click="isEditing = true">编辑</button>
</transition>
  • out-in:当前元素先离开,新元素等待旧元素离开后再进入
  • in-out:新元素先进入,旧元素等待新元素进入后再离开

JavaScript 钩子

html
<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave">
  <div v-if="show"></div>
</transition>
javascript
methods: {
  beforeEnter(el) {
    el.style.opacity = 0
  },
  enter(el, done) {
    Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
    Velocity(el, { fontSize: '1em' }, { complete: done })
  },
  afterEnter(el) {
    console.log('进入动画完成')
  },
  beforeLeave(el) {
    console.log('离开动画即将开始')
  },
  leave(el, done) {
    Velocity(el, { opacity: 0 }, { duration: 300, complete: done })
  },
  afterLeave(el) {
    console.log('离开动画完成')
  }
}

结合动态组件

html
<transition name="fade" mode="out-in">
  <component :is="currentComponent"></component>
</transition>

初始渲染过渡

html
<transition appear name="fade">
  <p>首次渲染时也会应用过渡效果</p>
</transition>

transition-group

功能描述

<transition-group> 为多个元素或组件列表提供过渡动画效果。与 <transition> 不同,它会渲染一个真实的 DOM 元素,并支持列表项的移动动画。

Props

属性类型默认值说明
tagstring'span'渲染的容器元素标签名
move-classstring-自定义移动过渡类名
其他属性--mode 外,支持 <transition> 的所有属性

事件

支持 <transition> 的所有事件。

列表排序动画

<transition-group> 使用 FLIP 动画技术实现平滑的位置移动动画:

html
<div id="app">
  <button @click="shuffle">随机排序</button>

  <transition-group name="list" tag="ul">
    <li v-for="item in items" :key="item.id" :key="item.id">{{ item.text }}</li>
  </transition-group>
</div>
css
.list-enter-active,
.list-leave-active {
  transition: all 0.5s;
}
.list-enter,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
.list-move {
  transition: transform 0.5s;
}
javascript
new Vue({
  el: "#app",
  data: {
    items: [
      { id: 1, text: "项目一" },
      { id: 2, text: "项目二" },
      { id: 3, text: "项目三" },
      { id: 4, text: "项目四" },
      { id: 5, text: "项目五" }
    ]
  },
  methods: {
    shuffle() {
      this.items = _.shuffle(this.items)
    }
  }
})

列表增删动画

html
<div id="app">
  <input v-model="newItem" @keyup.enter="addItem" />
  <button @click="addItem">添加</button>

  <transition-group name="list" tag="ul">
    <li v-for="(item, index) in items" :key="item.id">
      {{ item.text }}
      <button @click="removeItem(index)">删除</button>
    </li>
  </transition-group>
</div>
javascript
new Vue({
  el: "#app",
  data: {
    newItem: "",
    items: [],
    nextId: 1
  },
  methods: {
    addItem() {
      if (this.newItem.trim()) {
        this.items.push({
          id: this.nextId++,
          text: this.newItem.trim()
        })
        this.newItem = ""
      }
    },
    removeItem(index) {
      this.items.splice(index, 1)
    }
  }
})

注意事项

  • 必须为每个子元素设置唯一的 key,否则动画无法正常工作
  • 使用 v-for 时,key 的值必须是唯一且稳定的,避免使用索引作为 key
  • 移动动画依赖 CSS transform 属性,确保元素的 position 样式允许移动

keep-alive

功能描述

<keep-alive> 用于缓存不活动的组件实例,避免组件切换时被销毁和重新创建。它是一个抽象组件,不会渲染真实的 DOM 元素。被缓存的组件会触发 activateddeactivated 生命周期钩子。

Props

属性类型说明
includestring | RegExp | Array只有名称匹配的组件会被缓存
excludestring | RegExp | Array名称匹配的组件不会被缓存
maxnumber最多缓存多少组件实例

生命周期钩子

钩子触发时机
activated组件被激活(从缓存中恢复或首次创建)时调用
deactivated组件被停用(切换到其他组件)时调用

基础用法

html
<keep-alive>
  <component :is="currentComponent"></component>
</keep-alive>

条件缓存

html
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>

结合 transition 使用

html
<transition name="fade" mode="out-in">
  <keep-alive>
    <component :is="currentComponent"></component>
  </keep-alive>
</transition>

include 和 exclude

支持多种匹配方式:

html
<keep-alive include="Home,About">
  <component :is="currentComponent"></component>
</keep-alive>

<keep-alive :include="/Home|About/">
  <component :is="currentComponent"></component>
</keep-alive>

<keep-alive :include="['Home', 'About']">
  <component :is="currentComponent"></component>
</keep-alive>

<keep-alive exclude="Settings">
  <component :is="currentComponent"></component>
</keep-alive>

匹配规则:首先检查组件的 name 选项,如果 name 不存在,则匹配局部注册名称(父组件 components 选项的键)。匿名组件无法被匹配。

限制缓存数量

html
<keep-alive :max="10">
  <component :is="currentComponent"></component>
</keep-alive>

当缓存的实例数量超过 max 值时,最久未访问的实例会被销毁(LRU 策略)。

完整示例

html
<div id="app">
  <nav>
    <button @click="current = 'Home'">首页</button>
    <button @click="current = 'Posts'">文章</button>
    <button @click="current = 'Archive'">归档</button>
  </nav>

  <keep-alive>
    <component :is="current"></component>
  </keep-alive>
</div>
javascript
const Home = {
  template: "<div>首页 - {{ time }}</div>",
  data() {
    return {
      time: new Date().toLocaleTimeString()
    }
  },
  activated() {
    console.log("Home 被激活")
  },
  deactivated() {
    console.log("Home 被停用")
  }
}

const Posts = {
  template: "<div>文章列表</div>",
  activated() {
    console.log("Posts 被激活")
  },
  deactivated() {
    console.log("Posts 被停用")
  }
}

const Archive = {
  template: "<div>归档</div>",
  activated() {
    console.log("Archive 被激活")
  },
  deactivated() {
    console.log("Archive 被停用")
  }
}

new Vue({
  el: "#app",
  data: {
    current: "Home"
  },
  components: { Home, Posts, Archive }
})

注意事项

  • <keep-alive> 只能包含一个直属子组件
  • 如果包含 v-for,将不会正常工作
  • 函数式组件没有实例,无法被缓存
  • 被缓存的组件不会触发 createdmounted 等钩子,应使用 activated 替代

slot

功能描述

<slot> 是组件内容分发的出口,用于在组件模板中定义插槽位置。父组件传入的内容会被插入到 <slot> 标签所在的位置。

Props

属性类型说明
namestring插槽名称,用于具名插槽

默认插槽

html
<my-component>
  <p>这段内容会被插入到组件的默认插槽中</p>
</my-component>
html
<template id="my-component">
  <div class="container">
    <slot>默认内容(当父组件未提供内容时显示)</slot>
  </div>
</template>

具名插槽

html
<base-layout>
  <template v-slot:header>
    <h1>页面标题</h1>
  </template>

  <template v-slot:default>
    <p>主要内容</p>
  </template>

  <template v-slot:footer>
    <p>页脚信息</p>
  </template>
</base-layout>
html
<template id="base-layout">
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

作用域插槽

作用域插槽允许子组件向插槽传递数据:

html
<todo-list>
  <template v-slot:default="slotProps">
    <span class="custom-item"> {{ slotProps.item.text }} - {{ slotProps.item.priority }} </span>
  </template>
</todo-list>
html
<template id="todo-list">
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item"></slot>
    </li>
  </ul>
</template>
javascript
Vue.component("todo-list", {
  template: "#todo-list",
  data() {
    return {
      items: [
        { id: 1, text: "任务一", priority: "高" },
        { id: 2, text: "任务二", priority: "中" },
        { id: 3, text: "任务三", priority: "低" }
      ]
    }
  }
})

解构插槽 Props

html
<todo-list>
  <template v-slot="{ item }">
    <span>{{ item.text }}</span>
  </template>
</todo-list>

<todo-list>
  <template v-slot="{ item: todo }">
    <span>{{ todo.text }}</span>
  </template>
</todo-list>

<todo-list>
  <template v-slot="{ item, index = 0 }">
    <span>{{ index }}: {{ item.text }}</span>
  </template>
</todo-list>

插槽语法糖

v-slot 可缩写为 #

html
<base-layout>
  <template #header>
    <h1>页面标题</h1>
  </template>

  <template #default>
    <p>主要内容</p>
  </template>

  <template #footer>
    <p>页脚信息</p>
  </template>
</base-layout>

常见问题解答

Q1: 动态组件切换时如何保留状态?

使用 <keep-alive> 包裹 <component>

html
<keep-alive>
  <component :is="currentComponent"></component>
</keep-alive>

Q2: transition 动画不生效怎么办?

检查以下几点:

  1. 确保 CSS 类名正确(name-entername-enter-active 等)
  2. 确保过渡元素有 v-ifv-show 或动态 key
  3. 检查 CSS transitionanimation 属性是否正确设置

Q3: transition-group 列表动画错乱?

确保:

  1. 每个列表项有唯一且稳定的 key(不要使用索引)
  2. 为移动动画添加 .name-move
  3. 列表项的 CSS 不影响布局流(避免 display: inline-block

Q4: keep-alive 缓存的组件数据不更新?

使用 activated 钩子在组件激活时刷新数据:

javascript
export default {
  activated() {
    this.fetchData()
  }
}

Q5: 如何在 keep-alive 中强制刷新某个组件?

使用 :key 强制重新创建:

html
<keep-alive>
  <component :is="currentComponent" :key="componentKey"></component>
</keep-alive>
javascript
data() {
  return {
    componentKey: 0
  }
},
methods: {
  forceRefresh() {
    this.componentKey++
  }
}

最佳实践

1. 动态组件命名规范

使用语义化的组件名称,便于调试和维护:

javascript
components: {
  UserProfile: () => import('./UserProfile.vue'),
  UserSettings: () => import('./UserSettings.vue'),
  UserActivity: () => import('./UserActivity.vue')
}

2. 过渡动画性能优化

  • 优先使用 transformopacity 属性,避免触发重排
  • 使用 will-change 提示浏览器优化
css
.fade-enter-active,
.fade-leave-active {
  transition:
    opacity 0.3s,
    transform 0.3s;
  will-change: opacity, transform;
}

3. keep-alive 缓存策略

  • 只缓存需要保留状态的组件
  • 使用 max 限制缓存数量,避免内存泄漏
  • 使用 include/exclude 精确控制缓存范围

4. 插槽设计原则

  • 为插槽提供合理的默认内容
  • 使用具名插槽组织复杂布局
  • 作用域插槽用于需要子组件数据的场景

5. 组合使用内置组件

html
<transition name="fade" mode="out-in">
  <keep-alive include="Home,Posts">
    <component :is="currentComponent"></component>
  </keep-alive>
</transition>