普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月21日首页

Vue keep-alive 原理全解析(Vue2+Vue3适配)

2026年4月21日 11:55

Vue keep-alive 原理全解析(Vue2+Vue3适配)

Vue 中的 keep-alive 是一个内置抽象组件,核心作用是缓存组件实例,避免组件频繁创建和销毁,从而提升页面切换性能、保留组件状态(如表单输入、滚动位置)。它本身不会渲染 DOM,也不会出现在组件层级中,仅作为“容器”负责管理其包裹的组件的生命周期。

与 v-show(通过 CSS 控制显隐)、v-if(控制组件挂载/卸载)不同,keep-alive 是通过缓存组件实例实现状态保留,组件卸载时不会被销毁,而是被缓存到内存中,再次渲染时直接复用缓存的实例,无需重新执行 created、mounted 等生命周期钩子,这也是它提升性能的核心原因。

一、keep-alive 核心底层原理

keep-alive 的底层实现依赖 Vue 的组件生命周期钩子和缓存容器,核心逻辑分为“缓存存储”“缓存匹配”“实例复用”三个步骤,Vue2 和 Vue3 原理一致,仅底层 API 和缓存容器细节有细微差异。

1. 核心机制:缓存容器 + 生命周期拦截

keep-alive 内部维护了一个缓存对象(缓存容器) ,用于存储被包裹组件的实例,同时拦截组件的生命周期,修改其默认的挂载/卸载行为:

  • 当组件首次被渲染时,keep-alive 会将组件实例存入缓存容器,同时阻止组件的 destroy 钩子执行(避免实例被销毁);
  • 当组件被切换(路由跳转、v-if 切换)时,组件不会被卸载,而是被“缓存”起来,DOM 会被隐藏(并非删除);
  • 当组件再次被渲染时,keep-alive 会从缓存容器中取出之前缓存的实例,直接复用,无需重新创建,同时触发对应的缓存生命周期钩子。

2. 底层缓存容器实现(Vue2 vs Vue3)

keep-alive 的缓存容器本质是一个对象(或 Map),用于存储组件实例,key 通常是组件的 name(或内部生成的唯一标识),value 是组件实例本身,不同版本的实现略有差异:

// Vue2 底层缓存容器(简化版)
const cache = Object.create(null) // 用对象存储缓存,key为组件name,value为实例

// Vue3 底层缓存容器(简化版)
const cache = new Map() // 用Map存储缓存,key为组件name或唯一标识,value为实例

注意:keep-alive 缓存的是组件实例,而非 DOM 元素;DOM 元素会随着组件实例的缓存被保留,再次渲染时直接插入页面,避免重新渲染 DOM 的开销。

3. 生命周期拦截与重写

Vue 组件默认的生命周期是:创建(created)→ 挂载(mounted)→ 卸载(destroyed)。keep-alive 会拦截组件的 mounted 和 destroyed 钩子,并重写其行为:

  • 首次渲染:组件正常执行 created → mounted,执行完毕后,keep-alive 将实例存入缓存,同时标记组件为“已缓存”;
  • 缓存后再次渲染:不执行 created、mounted 钩子(避免重复初始化),直接复用缓存实例,触发 activated 钩子;
  • 组件被切换隐藏:不执行 destroyed 钩子(避免实例销毁),仅触发 deactivated 钩子,实例被保留在缓存中;
  • 缓存被清除:实例才会执行 destroyed 钩子,彻底销毁。

注意:只有被 keep-alive 包裹的组件,才会拥有 activated 和 deactivated 两个专属生命周期钩子,未被包裹的组件不会触发这两个钩子。

二、keep-alive 核心属性(控制缓存范围)

keep-alive 提供 3 个核心属性,用于控制缓存的组件范围,避免缓存过多组件导致内存占用过大,这是使用 keep-alive 时的关键配置,也是避免滥用缓存的核心:

1. include(白名单)

仅缓存名称匹配 include 的组件,支持字符串(逗号分隔)、数组、正则表达式。组件名称需与组件的 name 选项一致(不可省略),否则无法匹配。

<!-- 字符串:缓存 name 为 Home、About 的组件 -->
<keep-alive include="Home,About">
  <router-view />
</keep-alive>

<!-- 数组:缓存 Home、About 组件 -->
<keep-alive :include="['Home', 'About']">
  <router-view />
</keep-alive>

2. exclude(黑名单)

不缓存名称匹配 exclude 的组件,用法与 include 一致,优先级高于 include(若组件同时在两个名单中,以 exclude 为准,不缓存)。

<!-- 不缓存 name 为 Login 的组件 -->
<keep-alive exclude="Login">
  <router-view />
</keep-alive>

3. max(缓存数量限制)

限制缓存的组件实例数量,当缓存的实例数量超过 max 时,会按照“LRU(最近最少使用)”策略,删除最久未使用的缓存实例,避免内存泄漏。Vue2.5+ 新增该属性,Vue3 完全兼容。

<!-- 最多缓存 3 个组件实例,超过则删除最久未使用的 -->
<keep-alive :max="3">
  <router-view />
</keep-alive>

注意:LRU 策略是 keep-alive 内置的缓存淘汰机制,核心逻辑是“最近使用的组件优先保留,最久未使用的组件优先淘汰”,适用于需要缓存多个组件但担心内存占用的场景。

三、keep-alive 缓存逻辑细节

1. 缓存的匹配规则

keep-alive 匹配组件时,优先使用组件的 name 选项作为匹配依据,若组件未设置 name(或 name 为空),则无法被 include/exclude 匹配,也无法被缓存(Vue3 中未设置 name 的组件会被默认命名,但仍建议显式设置)。

注意:路由组件的 name 需与路由配置中的 name 保持一致,否则 include/exclude 无法匹配路由组件。

2. 组件状态的保留机制

keep-alive 缓存的是组件实例,因此组件内的 data 数据、表单输入、滚动位置等状态都会被保留:

  • 表单输入:缓存后再次进入组件,输入框中的内容不会丢失;
  • 滚动位置:缓存后再次进入组件,页面滚动条会停留在上一次离开时的位置;
  • 数据状态:组件内的 data 数据不会被重置,仍保持上一次的状态。

注意:若需要重置组件状态(如再次进入时清空表单),可在 activated 钩子中手动重置数据,因为 activated 钩子每次组件被激活时都会触发。

3. 动态组件与 keep-alive 的结合

keep-alive 常与动态组件(component 标签 + is 属性)结合使用,实现组件切换时的缓存:

<keep-alive include="ComponentA,ComponentB">
  <component :is="currentComponent" />
</keep-alive>

<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'

const currentComponent = ref('ComponentA') // 切换组件
</script>

此时,ComponentA 和 ComponentB 切换时,都会被缓存,避免频繁创建和销毁,提升切换流畅度。

4. Vue3 专属实战示例(可直接复制复用)

以下示例均适配 Vue3 组合式 API(

示例1:Vue3 路由缓存(最常用,配合 router-view)

<!-- App.vue 中使用,缓存指定路由组件 -->
<template>
  <div id="app">
    <router-link to="/home">首页</router-link>
    <router-link to="/list">列表页</router-link>
    <router-link to="/login">登录页</router-link>
    
    <!-- 缓存 Home、List 组件,排除 Login 组件 -->
    <keep-alive include="Home,List" exclude="Login" :max="2">
      <router-view />
    </keep-alive>
  </div>
</template>

<script setup>
// 无需额外引入,Vue3 内置 keep-alive
</script>

// 路由组件示例(List.vue,需显式设置name)
<template>
  <div>
    <h2>列表页</h2>
    <input v-model="keyword" placeholder="搜索关键词" />
    <ul>
      <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

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

// 必须显式设置组件name,否则keep-alive无法匹配
defineOptions({
  name: 'List'
})

const keyword = ref('')
const list = ref([
  { id: 1, name: 'Vue3 keep-alive 实战' },
  { id: 2, name: 'Vue3 组合式 API 用法' }
])

// 组件被激活时触发(每次进入都执行)
onActivated(() => {
  console.log('列表页被激活,可执行刷新数据等操作')
})

// 组件被缓存隐藏时触发
onDeactivated(() => {
  console.log('列表页被缓存,可执行清理操作')
})
</script>

示例2:Vue3 动态组件缓存(配合 component 标签)

<template>
  <div>
    <button @click="currentComponent = 'UserInfo'">用户信息</button>
    <button @click="currentComponent = 'UserSetting'">用户设置</button>
    
    <!-- 缓存 UserInfo、UserSetting 两个动态组件,限制最多缓存2个 -->
    <keep-alive include="UserInfo,UserSetting" :max="2">
      <component :is="currentComponent" />
    </keep-alive>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import UserInfo from './UserInfo.vue'
import UserSetting from './UserSetting.vue'

const currentComponent = ref('UserInfo')
</script>

// UserInfo.vue(需显式设置name)
<script setup>
defineOptions({
  name: 'UserInfo'
})
// 组件内容省略...
</script>

// UserSetting.vue(需显式设置name)
<script setup>
defineOptions({
  name: 'UserSetting'
})
// 组件内容省略...
</script>

示例3:Vue3 缓存组件状态重置(activated 钩子用法)

<template>
  <keep-alive include="FormPage">
    <component :is="currentComponent" />
  </keep-alive>
</template>

<script setup>
import { ref } from 'vue'
import FormPage from './FormPage.vue'

const currentComponent = ref('FormPage')
</script>

// FormPage.vue(缓存后重置表单状态)
<template>
  <form>
    <input v-model="form.name" placeholder="姓名" />
    <input v-model="form.age" placeholder="年龄" />
  </form>
</template>

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

defineOptions({
  name: 'FormPage'
})

const form = ref({
  name: '',
  age: ''
})

// 每次进入组件(被激活),重置表单状态
onActivated(() => {
  form.value = {
    name: '',
    age: ''
  }
})
</script>

示例4:Vue3 手动清除 keep-alive 缓存

<template>
  <div>
    <button @click="clearCache">清除列表页缓存</button>
    <keep-alive include="Home,List" ref="keepAliveRef">
      <router-view />
    </keep-alive>
  </div>
</template>

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

// 获取keep-alive实例
const keepAliveRef = ref(null)

// 手动清除指定组件的缓存(List组件)
const clearCache = () => {
  // cache 是keep-alive内部的缓存容器(Vue3为Map)
  const cache = keepAliveRef.value.cache
  // 遍历缓存,删除name为List的组件实例
  for (const [key, value] of cache.entries()) {
    if (value.type.name === 'List') {
      cache.delete(key)
      // 触发组件销毁(可选)
      value.component?.unmount()
    }
  }
}
</script>

说明:示例中所有组件均显式设置 name(Vue3 组合式 API 用 defineOptions 定义),确保 keep-alive 的 include/exclude 能正常匹配;所有代码可直接复制,替换组件名称和内容即可适配自身项目。

四、Vue3 keep-alive 核心缓存策略(重点)

Vue3 中 keep-alive 并非单一缓存逻辑,而是通过内置规则+属性配置+手动干预实现多层缓存控制,核心分为四大缓存策略,覆盖日常开发全场景,每类策略均对应底层逻辑和实战用法,避免缓存滥用和内存问题。

1. 全量默认缓存策略(基础无配置)

这是 keep-alive 最基础的缓存策略,不配置任何属性时默认生效,核心是缓存所有被包裹的组件实例,无筛选、无数量限制,适合仅需缓存单个组件的极简场景。

核心逻辑:组件首次挂载后存入内部 Map 缓存容器,切换时不销毁实例、仅隐藏 DOM,再次激活直接复用,全程仅执行一次 created、mounted 钩子。

<!-- 全量缓存示例:缓存 router-view 内所有路由组件 -->
<keep-alive>
  <router-view />
</keep-alive>

对应实战场景:适合单个路由组件缓存(如仅首页缓存),可将示例1中 include="Home,List" exclude="Login" :max="2" 简化为无任何属性配置,即 ,需注意避免多组件场景使用。

注意:该策略不适合多组件场景,会无限制占用内存,频繁切换多组件时严禁直接使用,必须搭配范围控制属性。

2. 范围筛选缓存策略(精准控制)

通过 include(白名单)exclude(黑名单) 两个属性实现精准筛选,是企业级开发最常用的策略,解决“只缓存需要保留状态的组件”核心需求,二者优先级:exclude > include。

(1)白名单缓存策略(include)

仅缓存组件 name 匹配的组件,未匹配组件完全不缓存,每次切换都会重新创建销毁,适合指定少数核心页面缓存。

<!-- 仅缓存 Home、List 两个路由组件 -->
<keep-alive :include="['Home','List']">
  <router-view />
</keep-alive>

对应实战示例:参考“示例1:Vue3 路由缓存”,其中 include="Home,List" 就是典型的白名单策略,仅缓存首页和列表页,排除登录页,贴合企业级路由缓存高频场景,与示例中配置完全匹配。

(2)黑名单缓存策略(exclude)

排除指定组件,其余被包裹组件全部缓存,适合大部分组件需要缓存、仅少数组件无需缓存的场景。

<!-- 缓存所有组件,排除 Login、Detail 组件 -->
<keep-alive exclude="Login,Detail">
  <router-view />
</keep-alive>

对应实战示例:可基于示例1修改,将 include="Home,List" 改为 exclude="Login",即可实现“缓存所有路由组件,仅排除登录页”,与示例1的路由缓存场景一致,适配大部分页面需缓存的业务需求。

关键要求:该策略依赖组件 name,Vue3 组合式API中必须用 defineOptions 显式声明 name,自动生成的默认name易匹配失败。

3. LRU 淘汰缓存策略(内存优化)

通过 max 属性配合内置 LRU(最近最少使用) 算法实现,是 Vue3 自带的内存保护策略,专门解决多组件缓存导致的内存溢出问题。

核心逻辑:设定最大缓存数量,当缓存实例数超过 max 值时,自动删除最久未被激活使用的组件缓存,保留近期高频使用的组件实例,平衡性能与内存占用。

<!-- 最多缓存3个组件,超出则触发LRU淘汰 -->
<keep-alive :include="['Home','List','User','Setting']" :max="3">
  <router-view />
</keep-alive>

对应实战示例:参考“示例2:Vue3 动态组件缓存”,其中 :max="2" 就是LRU淘汰策略的应用,限制缓存UserInfo和UserSetting两个组件,若新增组件切换(如新增UserCenter),会自动淘汰最久未使用的组件,与示例配置完全对应。

4. 手动干预缓存策略(灵活控制)

属于进阶策略,突破内置属性限制,通过 ref 获取 keep-alive 实例,直接操作内部 Map 缓存容器,实现手动清除指定/全部缓存,适合需要动态重置缓存的场景(如退出登录、表单提交后清空缓存)。

核心逻辑:Vue3 中 keep-alive 实例暴露 cache 属性(Map 类型),可通过遍历、删除键值对实现手动清缓存,还可配合组件 unmount 彻底销毁实例。

<template>
  <button @click="clearTargetCache">清空列表页缓存</button>
  <button @click="clearAllCache">清空全部缓存</button>
  <keep-alive ref="keepAliveRef" include="Home,List,User">
    <router-view />
  </keep-alive>
</template>

<script setup>
import { ref } from 'vue'
const keepAliveRef = ref(null)

// 手动清除指定组件(List)缓存
const clearTargetCache = () => {
  const cacheMap = keepAliveRef.value?.cache
  if (!cacheMap) return
  for (const [key, instance] of cacheMap.entries()) {
    if (instance.type.name === 'List') {
      cacheMap.delete(key)
      // 彻底销毁组件实例
      instance.component?.unmount()
    }
  }
}

// 手动清空所有缓存
const clearAllCache = () => {
  const cacheMap = keepAliveRef.value?.cache
  if (!cacheMap) return
  cacheMap.clear()
}
</script>

对应实战示例:完全匹配“示例4:Vue3 手动清除 keep-alive 缓存”,示例4中通过 ref 获取 keep-alive 实例、删除List组件缓存,与本策略“手动干预缓存”的核心逻辑、代码实现完全一致,可直接复制示例4代码适配自身项目。

缓存策略使用优先级(推荐)

日常开发优先按这个顺序选择,兼顾性能与易用性: 范围筛选策略(include/exclude)→ LRU淘汰策略(max)→ 手动干预策略 → 默认全量策略

策略与示例对应总结(无偏差):范围筛选策略(include/exclude)对应示例1(路由缓存)、示例2(动态组件缓存);LRU淘汰策略(max)对应示例2;手动干预策略对应示例4(手动清缓存);默认全量策略可基于示例1简化配置实现,各类策略与示例精准匹配,无偏差。

五、Vue2 与 Vue3 keep-alive 核心差异

keep-alive 的核心原理和用法在 Vue2 和 Vue3 中基本一致,主要差异集中在底层实现和部分细节,不影响日常使用:

对比维度 Vue2 Vue3
缓存容器 使用普通对象(Object)存储 使用 Map 存储,性能更优,支持更灵活的 key 类型
组件 name 要求 必须显式设置 name,否则无法匹配缓存 未显式设置 name 时,会自动生成默认名称(基于组件文件路径),但仍建议显式设置
生命周期钩子 activated/deactivated 钩子在组件内直接定义 选项式 API 用法与 Vue2 一致;组合式 API 中需使用 onActivated、onDeactivated 钩子
底层实现 基于 Vue 实例的 $destroy 方法拦截 基于组件的 unmount 生命周期拦截,与 Composition API 适配更友好
缓存策略拓展 仅基础筛选+LRU,无便捷手动清缓存方式 支持直接操作 Map 缓存,手动清缓存更便捷

注意:Vue3 中,keep-alive 不支持包裹多个根节点的组件,否则会抛出警告并失效,需确保被包裹的组件只有一个根节点。

六、常见使用场景与注意事项

1. 常见使用场景

  • 路由切换场景:如首页、列表页、详情页切换,缓存列表页状态(避免重新请求数据、重置滚动位置);
  • 动态组件切换场景:如标签页、步骤条,缓存每个标签/步骤的组件状态;
  • 表单场景:如长表单分页填写,缓存已填写的表单数据,避免切换分页时数据丢失。

2. 缓存策略专属注意事项

  • 范围策略必配name:使用include/exclude时,Vue3组合式API必须用defineOptions声明name,禁止依赖自动生成name;
  • LRU策略max值合理设置:max数值建议按业务高频页面数量设定,一般设3-5即可,不宜过大或过小;
  • 手动清缓存需彻底:删除缓存后建议调用unmount销毁实例,避免残留实例导致内存泄漏;
  • 禁止策略冲突:不同时配置冲突的include和exclude,避免缓存不生效;
  • 动态路由缓存适配:动态路由组件需保证name固定,否则范围策略匹配失效;
  • 缓存状态按需重置:即便用了缓存策略,仍需在onActivated钩子中处理状态重置,避免旧数据干扰。

3. 通用关键注意事项

  • 避免过度缓存:不要缓存所有组件,尤其是一次性使用、无需保留状态的组件(如登录页),否则会增加内存占用,反而影响性能;
  • 缓存组件的生命周期差异:被缓存的组件,created、mounted 仅执行一次,后续渲染仅触发 activated,卸载仅触发 deactivated;
  • 避免缓存带定时器/事件监听的组件:若组件内有定时器、事件监听,需在 deactivated 钩子中清除,在 activated 钩子中重新初始化,避免内存泄漏;
  • Vue3 多根组件限制:keep-alive 包裹的组件必须是单根节点,否则缓存失效并抛出警告。
昨天以前首页

Vue自定义指令全解析(Vue2+Vue3适配)| 底层DOM操作必备

2026年4月19日 11:02

Vue 除了提供 v-modelv-showv-bind 等内置指令外,还允许开发者注册自定义指令(Custom Directives),用于封装涉及普通元素的底层 DOM 访问逻辑,弥补内置指令的灵活性不足。自定义指令的核心作用是复用 DOM 相关的重复操作,无需在组件的生命周期钩子中编写大量冗余代码,尤其适合焦点控制、权限控制、输入校验、动画效果等场景,是 Vue 开发中提升代码复用性和可维护性的重要手段。

本文将详细讲解 Vue 自定义指令的核心概念、注册方式、钩子函数、参数说明,结合 Vue2 与 Vue3 的语法差异,提供可直接复制的实战示例和进阶用法,兼顾新手入门与企业级实战需求。

一、自定义指令核心基础(必懂)

1. 核心定位

自定义指令主要用于处理底层 DOM 操作,与组件、组合式函数形成互补:组件是主要的构建模块,组合式函数侧重于有状态的逻辑,而自定义指令则专注于 DOM 元素的直接操作。需要注意的是,若功能可通过 v-bind 等内置指令或组件实现,优先选择内置指令,因其更高效、对服务端渲染更友好。

2. 命名规范

自定义指令的命名需遵循以下规范,确保兼容性和可读性:

  • 指令名不包含 v- 前缀(注册时无需写,使用时必须加 v-);
  • 命名采用“小写字母 + 连字符”形式(如 v-focusv-permission),避免驼峰式(Vue3 中虽支持驼峰命名,但模板中仍需转为连字符形式);
  • 避免与 Vue 内置指令重名(如不能命名为 v-modelv-show)。

3. 核心分类

根据作用域,自定义指令分为两类,适配不同使用场景:

  • 全局指令:在整个 Vue 应用中注册,所有组件均可直接使用,适合通用型场景(如 v-focusv-loading);
  • 局部指令:仅在单个组件内注册,仅当前组件可用,适合组件专属的 DOM 操作场景。

二、自定义指令的注册方式(Vue2+Vue3对比)

Vue2 与 Vue3 的注册方式核心差异在于“全局注册的调用对象”,局部注册逻辑基本一致,以下是完整实战示例。

1. 全局注册(推荐通用指令使用)

// 1. Vue2 全局注册(main.js)
import Vue from 'vue'
import App from './App.vue'

// 全局注册 v-focus 指令(实现输入框自动聚焦)
Vue.directive('focus', {
  // 钩子函数(后续详解)
  mounted(el) {
    el.focus() // 直接操作DOM元素
  }
})

new Vue({
  render: h => h(App)
}).$mount('#app')

// 2. Vue3 全局注册(main.js)
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 全局注册 v-focus 指令,语法与Vue2一致,仅注册对象不同
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

app.mount('#app')

2. 局部注册(推荐组件专属指令使用)

// 1. Vue2 局部注册(组件内)
<template>
  <input v-focus type="text" placeholder="自动聚焦输入框" />
</template>

<script>
export default {
  // 局部注册指令,仅当前组件可用
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}
</script>

// 2. Vue3 局部注册(选项式API,与Vue2一致)
<template>
  <input v-focus type="text" placeholder="自动聚焦输入框" />
</template>

<script>
export default {
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}
</script>

// 3. Vue3 局部注册(组合式API,<script setup><template>
  <input v-focus type="text" placeholder="自动聚焦输入框" />
</template>

<script setup>
// 组合式API中,直接定义以v开头的驼峰变量,即可完成局部注册
// 变量名vFocus,模板中使用时需转为v-focus
const vFocus = {
  mounted(el) {
    el.focus()
  }
}
</script>

说明:Vue3 组合式 API 中,无需在 directives 选项中注册,只要定义以 v 开头的驼峰式变量(如 vFocus),即可在模板中以 v-focus 形式使用,简化了局部注册流程。

三、自定义指令的钩子函数(核心)

自定义指令的本质是一组钩子函数的集合,用于在指令生命周期的不同阶段执行 DOM 操作。Vue2 与 Vue3 的钩子函数名称和执行时机有差异,核心逻辑一致,以下分版本详解。

1. Vue3 钩子函数(7个,推荐)

Vue3 提供 7 个钩子函数,覆盖指令从绑定到卸载的完整生命周期,按执行顺序排列如下:

app.directive('custom', {
  // 1. created:指令绑定到元素后立即调用(元素未插入DOM,无法操作DOM)
  created(el, binding, vnode) {},
  // 2. beforeMount:元素被插入DOM前调用
  beforeMount(el, binding, vnode) {},
  // 3. mounted:元素被插入DOM后调用(最常用,适合执行初始化DOM操作)
  mounted(el, binding, vnode) {},
  // 4. beforeUpdate:包含指令的组件更新前调用(子组件未更新)
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 5. updated:包含指令的组件更新后调用(子组件已更新,适合更新DOM状态)
  updated(el, binding, vnode, prevVnode) {},
  // 6. beforeUnmount:元素被卸载前调用(可做清理前准备)
  beforeUnmount(el, binding, vnode) {},
  // 7. unmounted:元素被卸载后调用(必须清理资源,避免内存泄漏)
  unmounted(el, binding, vnode) {}
})

2. Vue2 钩子函数(5个)

Vue2 的钩子函数与 Vue3 对应,名称和执行时机略有差异,核心功能一致,按执行顺序排列如下:

Vue.directive('custom', {
  // 1. bind:指令第一次绑定到元素时调用(仅一次,元素未插入DOM,可做初始化设置)
  bind(el, binding, vnode) {},
  // 2. inserted:元素被插入父节点时调用(仅保证父节点存在,不一定插入文档)
  inserted(el, binding, vnode) {},
  // 3. update:包含指令的组件VNode更新时调用(子组件可能未更新)
  update(el, binding, vnode, oldVnode) {},
  // 4. componentUpdated:组件VNode及其子VNode全部更新后调用
  componentUpdated(el, binding, vnode, oldVnode) {},
  // 5. unbind:指令与元素解绑时调用(仅一次,用于清理资源)
  unbind(el, binding, vnode) {}
})

3. 钩子函数参数(Vue2/Vue3通用)

所有钩子函数都会接收 4 个固定参数(顺序不可变),除 el 外,其他参数均为只读,不可修改,若需共享数据,可通过 el 的自定义属性实现:

  • el:指令绑定的真实 DOM 元素,可直接操作(如el.focus()el.style.color = 'red');

  • binding:指令绑定信息的对象,核心属性如下:

    • value:传递给指令的值(如 v-custom="100",value 为 100);
    • oldValue:指令的旧绑定值,仅在 update/componentUpdated(Vue2)、beforeUpdate/updated(Vue3)中可用;
    • arg:指令的参数(如 v-custom:click,arg 为 'click');
    • modifiers:指令的修饰符对象(如 v-custom.prevent,modifiers 为 { prevent: true });
    • name:指令名(不包含 v- 前缀)。
  • vnode:Vue 编译生成的虚拟节点,描述 DOM 元素的结构;

  • prevVnode(Vue3)/ oldVnode(Vue2):上一个虚拟节点,仅在更新相关钩子中可用。

4. 钩子函数简化写法

若仅需使用 mounted(Vue3)或 bind + inserted(Vue2)一个钩子函数,可简化为函数形式,无需定义完整的钩子对象:

// Vue3 简化写法(仅使用mounted钩子)
app.directive('focus', (el) => {
  el.focus() // 等同于 { mounted: (el) => el.focus() }
})

// Vue2 简化写法(等同于 bind + inserted 钩子执行相同逻辑)
Vue.directive('focus', (el) => {
  el.focus()
})

四、Vue2与Vue3自定义指令核心差异汇总

为方便快速区分和项目迁移,整理核心差异如下,重点关注钩子函数和注册方式的差异:

对比维度 Vue2 Vue3
全局注册方式 Vue.directive('指令名', 钩子对象/函数) app.directive('指令名', 钩子对象/函数)
局部注册方式 仅支持 directives 选项注册 支持 directives 选项 +
钩子函数 bind、inserted、update、componentUpdated、unbind(5个) created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted(7个)
核心差异点 无 created、beforeMount、beforeUnmount 钩子 新增3个钩子,完善生命周期覆盖;支持组合式API集成
虚拟节点参数 update/componentUpdated 接收 oldVnode beforeUpdate/updated 接收 prevVnode

五、实战示例(可直接复制使用)

以下示例覆盖企业级开发中高频场景,适配 Vue2 和 Vue3,标注清晰,复制后可直接集成到项目中。

示例1:v-focus(自动聚焦,基础示例)

实现输入框挂载后自动聚焦,比原生 autofocus 属性更实用,可在 Vue 动态插入元素时生效。

// Vue3 实现(全局注册,main.js)
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
// 自动聚焦指令
app.directive('focus', {
  mounted(el) {
    el.focus() // 元素挂载后执行聚焦
  }
})
app.mount('#app')

// 模板中使用(所有组件均可使用)
<template>
  <input v-focus type="text" placeholder="自动聚焦输入框" />
</template>

// Vue2 实现(全局注册,main.js)
import Vue from 'vue'
import App from './App.vue'

Vue.directive('focus', {
  inserted(el) {
    el.focus() // Vue2 用inserted钩子,确保元素已插入DOM
  }
})

new Vue({
  render: h => h(App)
}).$mount('#app')

示例2:v-permission(权限控制,后台系统必用)

根据用户权限控制元素显隐,无对应权限则移除元素,适用于按钮、菜单等权限管控场景。

// Vue3 实现(全局注册,directives/permission.js)
import { useUserStore } from '@/stores/user' // 假设使用Pinia管理用户状态

export default {
  mounted(el, binding) {
    const userStore = useUserStore()
    const permission = binding.value // 接收权限码(如 'user:add')
    if (!permission) return
    // 无权限则移除元素
    if (!userStore.permissions.includes(permission)) {
      el.parentNode?.removeChild(el)
    }
  }
}

// main.js 引入注册
import permission from './directives/permission'
app.directive('permission', permission)

// 模板中使用
<button v-permission="'user:add'">添加用户</button>
<button v-permission="'user:delete'">删除用户</button>

// Vue2 实现(全局注册)
import Vue from 'vue'
import store from './store' // Vuex管理用户状态

Vue.directive('permission', {
  inserted(el, binding) {
    const permission = binding.value
    if (!permission) return
    if (!store.state.user.permissions.includes(permission)) {
      el.parentNode?.removeChild(el)
    }
  }
})

示例3:v-debounce(防抖点击,防重复提交)

实现按钮点击防抖,避免用户快速点击导致重复请求,适用于搜索、提交等场景。

// Vue3 实现(局部注册,组件内)
<script setup>
// 防抖指令
const vDebounce = {
  mounted(el, binding) {
    const { func, delay = 300 } = binding.value // 接收函数和延迟时间
    let timer = null
    // 绑定点击事件,实现防抖
    el.addEventListener('click', () => {
      clearTimeout(timer)
      timer = setTimeout(() => func(), delay)
    })
    // 卸载时清理定时器,避免内存泄漏
    el._timer = timer
  },
  unmounted(el) {
    clearTimeout(el._timer)
  }
}

// 点击事件
const handleSubmit = () => {
  console.log('提交表单')
}
</script>

<template>
  <button v-debounce="{ func: handleSubmit, delay: 500 }">提交</button>
</template>

示例4:v-lazy(图片懒加载,性能优化)

实现图片懒加载,当图片进入视口后再加载,减少首屏资源请求,提升加载速度。

// Vue3 实现(全局注册)
app.directive('lazy', {
  mounted(el, binding) {
    // 监听元素是否进入视口
    const observer = new IntersectionObserver(([{ isIntersecting }]) => {
      if (isIntersecting) {
        el.src = binding.value // 进入视口后加载图片
        observer.unobserve(el) // 加载完成后停止监听
      }
    })
    observer.observe(el) // 开始监听元素
  }
})

// 模板中使用(src绑定占位图,v-lazy绑定真实图片地址)
<template>
  <img v-lazy="realImgUrl" src="placeholder.png" alt="懒加载图片" />
</template>

六、自定义指令进阶用法

1. 指令传递动态参数和修饰符

通过 arg 传递动态参数,modifiers 传递修饰符,实现更灵活的指令逻辑:

<template>
  <!-- 动态参数:click(触发事件),修饰符:prevent(阻止默认行为) -->
  <button v-custom:click.prevent="handleClick">点击触发</button>
</template>

<script setup>
const vCustom = {
  mounted(el, binding) {
    const event = binding.arg // 接收动态参数:click
    const { prevent } = binding.modifiers // 接收修饰符:prevent
    // 绑定事件
    el.addEventListener(event, (e) => {
      // 若有prevent修饰符,阻止默认行为
      if (prevent) e.preventDefault()
      binding.value() // 执行传递的函数
    })
  }
}

const handleClick = () => {
  console.log('点击事件触发')
}
</script>

2. 指令与组件实例交互

Vue3 中,可通过 binding.instance 访问使用指令的组件实例,实现指令与组件的联动:

app.directive('custom', {
  mounted(el, binding) {
    // 访问组件实例的data、methods
    const componentInstance = binding.instance
    console.log(componentInstance.msg) // 访问组件的msg数据
    componentInstance.handleMethod() // 调用组件的方法
  }
})

3. 指令模块化封装

对于大型项目,可将通用指令封装为独立模块,统一管理,便于复用和维护:

// 1. 新建 directives/index.js(指令入口)
import focus from './focus'
import permission from './permission'
import debounce from './debounce'

// 批量注册全局指令
export default (app) => {
  app.directive('focus', focus)
  app.directive('permission', permission)
  app.directive('debounce', debounce)
}

// 2. main.js 引入
import installDirectives from './directives'
const app = createApp(App)
installDirectives(app) // 批量注册所有指令
app.mount('#app')

七、自定义指令使用注意事项

1. 避免过度使用

自定义指令仅用于底层 DOM 操作,若功能可通过组件、Props、组合式函数实现,优先选择其他方式,避免滥用指令导致代码逻辑混乱。

2. 必须清理资源

unbind(Vue2)或 unmounted(Vue3)钩子中,必须清理指令绑定的事件监听器、定时器、观察者等资源,避免内存泄漏(如示例中防抖指令清理定时器)。

3. 不依赖 DOM 结构

指令操作的 DOM 元素可能被动态渲染或删除,需做好容错处理(如使用 el.parentNode?.removeChild(el),避免父节点不存在导致报错)。

4. 区分 Vue2/Vue3 钩子差异

Vue2 中,若需操作已插入 DOM 的元素,需使用 inserted钩子;Vue3 中,对应使用 mounted 钩子,避免因钩子使用错误导致 DOM 操作失效。

5. 支持 TypeScript 类型定义

Vue3 中,可通过扩展 ComponentCustomProperties 接口,为自定义全局指令添加 TypeScript 类型,提升类型安全性和开发体验。

八、总结

Vue 自定义指令的核心是封装底层 DOM 操作逻辑,实现代码复用,其核心用法可总结为:

  • 注册方式:全局注册(通用指令)、局部注册(组件专属指令),Vue3 组合式 API 简化了局部注册流程;
  • 核心逻辑:通过钩子函数在指令生命周期的不同阶段执行 DOM 操作,钩子参数提供了指令绑定的关键信息;
  • 适用场景:焦点控制、权限控制、防抖节流、图片懒加载、输入校验等需直接操作 DOM 的场景;
  • 版本差异:重点区分 Vue2 与 Vue3 的钩子函数和注册方式,便于项目迁移和兼容。

本文所有示例均可直接复制到项目中使用,只需根据 Vue 版本调整钩子函数和注册方式,即可快速适配实战需求。合理使用自定义指令,能有效减少冗余代码,提升项目的可维护性和开发效率。

Vue插槽用法全解析(Vue2+Vue3适配)| 组件复用必备

2026年4月19日 10:49

Vue插槽(Slot)是组件间内容分发的核心机制,用于解决“父组件向子组件传递模板片段”的需求,实现组件的灵活复用与结构解耦。简单来说,插槽就是子组件中预留的“内容占位符”,占位符的具体内容由父组件决定,子组件仅负责固定布局和逻辑,让组件既能保持统一风格,又能灵活适配不同场景。

本文将详细讲解Vue插槽的核心概念、3种核心用法(默认插槽、具名插槽、作用域插槽),明确Vue2与Vue3的语法差异,提供可直接复制的实战示例,同时梳理常见问题,兼顾新手入门与实战开发需求。

一、插槽核心基础(必懂)

插槽的核心逻辑可类比为“函数传参”:父组件向子组件传递“模板内容”(相当于函数参数),子组件通过<slot>标签(相当于函数接收参数的位置)接收并渲染内容,最终实现“子组件定结构、父组件定内容”的复用效果。

核心要点:

  • 插槽内容可是任意合法模板(文本、标签、组件等),不局限于简单文本;
  • 插槽内容的作用域:插槽内容定义在父组件,因此只能访问父组件的数据,无法直接访问子组件的数据(需用作用域插槽解决);
  • Vue2与Vue3插槽核心功能一致,仅在具名插槽、作用域插槽的语法上有差异,下文将分别标注适配版本。

二、Vue插槽3种核心用法(实战重点)

按“基础到复杂”排序,默认插槽适用于简单内容分发,具名插槽适用于多区域内容分发,作用域插槽适用于子组件向父组件传递数据后,父组件自定义渲染内容。

1. 默认插槽(匿名插槽)—— 最简单的内容分发

默认插槽是最基础的插槽形式,子组件中仅定义一个无名称的<slot>标签,父组件传入的所有未命名内容,都会自动填充到这个插槽中。Vue2与Vue3用法基本一致。

实战示例(Vue2+Vue3通用)

// 1. 子组件(SlotDefault.vue)—— 定义默认插槽
<template>
  <div class="slot-container">
    <!-- 插槽出口:未命名,即为默认插槽 --&gt;
    &lt;slot&gt;
      <!-- 后备内容(默认内容):父组件未传入内容时显示 -->
      这是默认插槽的后备内容(父组件未传内容时显示)
    </slot>
  </div>
</template>

<style scoped>
.slot-container {
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
}
</style>

// 2. 父组件 —— 使用默认插槽
<template>
  <div>
    <h3>默认插槽用法</h3>
    <!-- 方式1:传入简单文本 -->
    <SlotDefault>父组件传入的简单文本内容</SlotDefault>

    <!-- 方式2:传入复杂内容(标签+组件) -->
    <SlotDefault>
      <span style="color: #42b983;">父组件传入的带样式文本</span>
      <button>父组件传入的按钮</button>
      <!-- 传入其他组件 -->
      <OtherComponent />
    </SlotDefault>

    <!-- 方式3:不传入内容(显示子组件的后备内容) -->
    <SlotDefault />
  </div>
</template>

<script setup>
// Vue3 需引入子组件
import SlotDefault from './SlotDefault.vue'
import OtherComponent from './OtherComponent.vue'
</script>

// Vue2 脚本写法(父组件)
<script>
import SlotDefault from './SlotDefault.vue'
import OtherComponent from './OtherComponent.vue'
export default {
  components: { SlotDefault, OtherComponent }
}
</script>

说明:子组件<slot>标签内的内容为“后备内容”,仅当父组件未传入任何插槽内容时才会显示,传入内容后会自动替换后备内容。

2. 具名插槽 —— 多区域内容精准分发

当子组件需要多个不同的内容占位区域(如页面布局的头部、主体、底部)时,默认插槽无法满足需求,此时需使用具名插槽。通过给<slot>标签添加name属性命名,父组件可精准将内容分发到对应插槽,Vue2与Vue3语法差异较大。

实战示例(Vue2 vs Vue3)

// 1. 子组件(SlotNamed.vue)—— 定义具名插槽(Vue2+Vue3通用)
<template>
  <div class="layout">
    <!-- 头部插槽:name="header" -->
    <slot name="header">默认头部</slot>
    
    <!-- 主体插槽:name="main" -->
    <slot name="main">默认主体</slot>
    
    <!-- 底部插槽:name="footer" -->
    <slot name="footer">默认底部</slot>
  </div>
</template>

<style scoped>
.layout {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.layout > div { padding: 10px; border: 1px solid #eee; }
</style>

// 2. 父组件使用 —— Vue2 写法
<template>
  <SlotNamed>
    <!-- 用 slot 属性指定插槽名称,已废弃(Vue2.6+推荐用v-slot) -->
    <div slot="header">Vue2 头部内容(自定义)</div>
    <div slot="main">Vue2 主体内容(自定义)</div>
    <div slot="footer">Vue2 底部内容(自定义)</div>
    
    <!-- Vue2.6+ 推荐写法:template + v-slot -->
    <template v-slot:header>
      <div>Vue2.6+ 头部内容(自定义)</div>
    </template>
    <template v-slot:main>
      <div>Vue2.6+ 主体内容(自定义)</div>
    </template>
    <template v-slot:footer>
      <div>Vue2.6+ 底部内容(自定义)</div>
    </template>
  </SlotNamed>
</template>

// 3. 父组件使用 —— Vue3 写法(核心:废弃slot属性,统一用v-slot)
<template>
  <SlotNamed>
    <!-- 语法:template + v-slot:插槽名,可简写为 #插槽名 -->
    <template #header>
      <div>Vue3 头部内容(自定义)</div>
    </template>
    <template #main>
      <div>Vue3 主体内容(自定义)</div>
    </template>
    <template #footer>
      <div>Vue3 底部内容(自定义)</div>
    </template>
    
    <!-- 未命名内容,自动分发到默认插槽(若子组件有默认插槽) -->
    <div>默认插槽内容(未命名)</div>
  </SlotNamed>
</template>

<script setup>
import SlotNamed from './SlotNamed.vue'
</script>

关键差异:Vue2支持slot属性和v-slot两种写法,Vue3仅支持v-slot(简写为#),且必须配合<template>标签使用(默认插槽可省略<template>)。

3. 作用域插槽 —— 子传父数据+父自定义渲染

默认插槽和具名插槽,只能实现“父组件向子组件传递内容”,无法让插槽内容访问子组件的数据。作用域插槽解决了这一问题:子组件通过v-bind将自身数据绑定到<slot>标签上(称为“插槽属性”),父组件接收这些数据后,可根据子组件数据自定义插槽内容的渲染方式。

核心场景:子组件有数据(如列表数据),但渲染样式由父组件决定(如列表项可渲染为文字、按钮、卡片)。

实战示例(Vue2 vs Vue3)

// 1. 子组件(SlotScoped.vue)—— 绑定子组件数据(Vue2+Vue3通用)
<template>
  <div class="list">
    <!-- 子组件数据:列表数组 -->
    <div v-for="(item, index) in list" :key="index">
      <!-- 绑定子组件数据到插槽::item="item" :index="index" -->
      <slot :item="item" :index="index"&gt;
        <!-- 后备内容父组件未自定义渲染时显示 -->
        {{ item.name }}(默认渲染)
      </slot>
    </div>
  </div>
</template>

<script setup>
// Vue3 脚本
import { ref } from 'vue'
const list = ref([
  { id: 1, name: 'Vue基础', type: '前端' },
  { id: 2, name: '插槽用法', type: '前端' },
  { id: 3, name: '路由跳转', type: '前端' }
])
</script>

// Vue2 脚本(子组件)
<script>
export default {
  data() {
    return {
      list: [
        { id: 1, name: 'Vue基础', type: '前端' },
        { id: 2, name: '插槽用法', type: '前端' },
        { id: 3, name: '路由跳转', type: '前端' }
      ]
    }
  }
}
</script>

// 2. 父组件使用 —— Vue2 写法
<template>
  <SlotScoped>
    <!-- 方式1:slot-scope 接收插槽属性(Vue2.6-) -->
    <div slot-scope="slotProps">
      索引:{{ slotProps.index }} | 名称:{{ slotProps.item.name }}
    </div>
    
    <!-- 方式2:v-slot 接收(Vue2.6+ 推荐,可解构) -->
    <template v-slot:default="slotProps">
      <!-- 解构简化:直接提取item和index -->
      <template v-slot:default="{ item, index }">
        索引{{ index + 1 }}:{{ item.name }}({{ item.type }})
      </template>
    </template>
  </SlotScoped>
</template>

// 3. 父组件使用 —— Vue3 写法(核心:废弃slot-scope,统一用v-slot接收)
<template>
  <SlotScoped>
    <!-- 方式1:完整写法,接收所有插槽属性 -->
    <template #default="slotProps">
      索引:{{ slotProps.index }} | 名称:{{ slotProps.item.name }}
    </template>
    
    <!-- 方式2:解构简化(推荐),可设置默认值避免报错 -->
    <template #default="{ item = { name: '默认名称' }, index = 0 }">
      索引{{ index + 1 }}:{{ item.name }}({{ item.type }})
      <button @click="handleClick(item.id)">查看详情</button>
    </template>
  </SlotScoped>
</template>

<script setup>
import SlotScoped from './SlotScoped.vue'
const handleClick = (id) => {
  console.log('查看ID为', id, '的详情')
}
</script>

关键差异:Vue2用slot-scopev-slot接收插槽属性,Vue3仅用v-slot接收,且支持ES6解构赋值,可设置默认值提升组件健壮性。

三、Vue2与Vue3插槽语法差异汇总

为方便快速区分和迁移,整理核心差异如下,重点关注Vue3的语法规范:

插槽类型 Vue2 语法 Vue3 语法 核心差异
默认插槽 直接在子组件标签内写内容;支持

Vue路由跳转全场景实战(Vue2+Vue3适配)| 新手必看

2026年4月18日 12:50

Vue路由跳转是前端项目页面切换的核心操作,贯穿整个Vue项目开发(从简单页面跳转,到带参跳转、权限控制跳转)。本文将整合Vue2、Vue3路由跳转的所有常用方式,明确不同场景的使用选择,补充参数传递、导航守卫、常见问题及解决方案,提供可直接复制的实战示例,兼顾新手入门与实战适配。

一、Vue路由跳转核心前提(必看)

无论Vue2还是Vue3,路由跳转前需确保已完成路由配置(引入Vue Router、创建路由实例、挂载路由),基础配置如下(简化版,可直接复用):

// Vue2 基础路由配置(router/index.js)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)

const routes = [
  { path: '/', name: 'Home', component: () => import('../views/Home.vue') },
  { path: '/about', name: 'About', component: () => import('../views/About.vue') },
  { path: '/user/:id', name: 'User', component: () => import('../views/User.vue') }
]

const router = new VueRouter({ mode: 'history', routes })
export default router

// Vue3 基础路由配置(router/index.js)
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
  { path: '/', name: 'Home', component: () => import('../views/Home.vue') },
  { path: '/about', name: 'About', component: () => import('../views/About.vue') },
  { path: '/user/:id', name: 'User', component: () => import('../views/User.vue') }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})
export default router

说明:Vue2需通过Vue.use(VueRouter)注册路由,Vue3通过createRouter创建路由实例,二者跳转语法有细微差异,下文将分别说明并标注适配版本。

二、Vue路由跳转3种核心方式(实战常用)

Vue路由跳转主要分为「声明式跳转」和「编程式跳转」,其中编程式跳转更灵活,可结合业务逻辑(如登录判断)使用,声明式跳转适合简单页面切换。

方式1:声明式跳转( 标签,最简洁)

核心:通过Vue Router提供的<router-link>标签实现跳转,无需写JS逻辑,自动渲染为a标签(可通过tag属性修改标签类型),适配Vue2、Vue3,用法完全一致。

1. 基础跳转(无参数)

<!-- 方式1:通过path跳转(推荐,简洁直观) -->
<router-link to="/">首页</router-link>
<router-link to="/about">关于我们</router-link>

<!-- 方式2:通过name跳转(需配置路由name,适合路径较长场景) -->
<router-link :to="{ name: 'Home' }">首页</router-link>
<router-link :to="{ name: 'About' }">关于我们</router-link>

<!-- 可选:修改渲染标签(默认a标签,改为button) -->
<router-link to="/" tag="button">首页(按钮形式)</router-link>

2. 带参数跳转(query参数 / params参数)

跳转时传递参数,用于页面间数据交互,两种参数类型用法不同,需区分场景:

<!-- 1. query参数(暴露在URL上,可刷新保留,适合简单数据) -->
<!-- 方式:path/name + query对象 -->
<router-link :to="{ path: '/user', query: { id: 1, name: '张三' } }">
  进入用户页(query参数)
</router-link>
<router-link :to="{ name: 'User', query: { id: 1, name: '张三' } }">
  进入用户页(name+query)
</router-link>
<!-- 跳转后URL:http://localhost:8080/user?id=1&name=张三 -->

<!-- 2. params参数(不暴露在URL上,刷新丢失,适合敏感数据) -->
<!-- 注意:params必须配合name跳转,不能配合path -->
<router-link :to="{ name: 'User', params: { id: 1, name: '张三' } }">
  进入用户页(params参数)
</router-link>
<!-- 跳转后URL:http://localhost:8080/user/1(需配置路由path为/user/:id) -->

方式2:编程式跳转(router.push / router.replace,最灵活)

核心:通过JS代码调用router.push(保留历史记录)或router.replace(不保留历史记录,无法返回上一页)实现跳转,适合结合业务逻辑(如登录成功后跳转、按钮点击跳转),Vue2和Vue3用法略有差异。

1. Vue2 编程式跳转

// 1. 基础跳转(无参数)
this.$router.push('/') // path跳转
this.$router.push({ name: 'Home' }) // name跳转

// 2. 带参数跳转(query / params)
// query参数
this.$router.push({
  path: '/user',
  query: { id: 1, name: '张三' }
})
// params参数(需配合name)
this.$router.push({
  name: 'User',
  params: { id: 1, name: '张三' }
})

// 3. 替换跳转(不保留历史记录)
this.$router.replace('/about')

// 4. 后退/前进(操作历史记录)
this.$router.go(-1) // 后退1页(类似浏览器返回键)
this.$router.back() // 等同于go(-1)
this.$router.go(1) // 前进1页

2. Vue3 编程式跳转

Vue3 setup语法中,无this,需通过useRouter引入路由实例,用法如下:

// 1. 引入路由实例(必须先引入)
import { useRouter } from 'vue-router'
const router = useRouter()

// 2. 基础跳转(无参数)
router.push('/')
router.push({ name: 'Home' })

// 3. 带参数跳转(query / params)
router.push({
  path: '/user',
  query: { id: 1, name: '张三' }
})
router.push({
  name: 'User',
  params: { id: 1, name: '张三' }
})

// 4. 替换跳转(不保留历史记录)
router.replace('/about')

// 5. 后退/前进
router.go(-1)
router.back()
router.go(1)

方式3:路由重定向(redirect,自动跳转)

核心:在路由配置中通过redirect属性,实现页面自动跳转(无需用户操作),适合默认页面、404页面、旧路径跳转新路径场景,Vue2、Vue3用法一致。

// 路由配置中添加redirect
const routes = [
  // 1. 默认跳转(访问根路径,自动跳转到首页)
  { path: '/', redirect: '/home' },
  // 2. 通过name重定向
  { path: '/index', redirect: { name: 'Home' } },
  // 3. 旧路径跳转新路径(兼容旧链接)
  { path: '/old-user', redirect: '/user' },
  // 4. 404页面(匹配所有未定义路径,跳转到404组件)
  { path: '/:pathMatch(.*)*', redirect: '/404' }
]

三、路由跳转参数接收(配套必备)

跳转时传递的query/params参数,需在目标页面接收后使用,Vue2和Vue3接收方式不同,以下是完整示例:

1. Vue2 参数接收

// 1. 接收query参数
export default {
  mounted() {
    const id = this.$route.query.id // 接收query参数id
    const name = this.$route.query.name // 接收query参数name
    console.log(id, name) // 输出:1 张三
  }
}

// 2. 接收params参数
export default {
  mounted() {
    const id = this.$route.params.id // 接收params参数id
    const name = this.$route.params.name // 接收params参数name
    console.log(id, name) // 输出:1 张三
  }
}

2. Vue3 参数接收

Vue3 setup语法中,需通过useRoute引入路由对象,接收参数:

// 引入路由对象
import { useRoute } from 'vue-router'
const route = useRoute()

// 接收参数(可在setup中直接使用,或在生命周期中使用)
const id = route.query.id // query参数
const name = route.query.name

const paramsId = route.params.id // params参数
const paramsName = route.params.name

console.log(id, paramsId) // 输出:1 1

四、路由跳转进阶:导航守卫(权限控制)

实际开发中,常需要对路由跳转进行权限控制(如未登录不能访问个人中心),此时需使用导航守卫,拦截跳转并判断权限,Vue2、Vue3用法基本一致,以下是实战示例:

1. 全局导航守卫(控制所有路由跳转)

// Vue2 全局导航守卫(router/index.js)
router.beforeEach((to, from, next) => {
  // to:目标路由对象
  // from:当前跳转前的路由对象
  // next:放行/拦截方法
  
  // 示例:未登录不能访问/user路径
  const token = localStorage.getItem('token') // 模拟登录状态
  if (to.path === '/user' && !token) {
    next('/login') // 未登录,拦截并跳转到登录页
  } else {
    next() // 已登录,放行
  }
})

// Vue3 全局导航守卫(用法完全一致)
router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token')
  if (to.meta.requireAuth && !token) { // 结合路由元信息,更灵活
    next('/login')
  } else {
    next()
  }
})

// 路由元信息配置(标记需要权限的路由)
const routes = [
  {
    path: '/user',
    name: 'User',
    component: () => import('../views/User.vue'),
    meta: { requireAuth: true } // 标记:需要登录才能访问
  }
]

2. 组件内导航守卫(控制单个组件跳转)

仅对当前组件的跳转进行拦截,适合单个组件的特殊权限控制:

// Vue2 组件内守卫
export default {
  // 进入组件前拦截
  beforeRouteEnter(to, from, next) {
    const token = localStorage.getItem('token')
    if (!token) {
      next('/login')
    } else {
      next()
    }
  },
  // 离开组件前拦截(如提示用户未保存内容)
  beforeRouteLeave(to, from, next) {
    if (confirm('确定要离开吗?内容未保存')) {
      next()
    } else {
      next(false) // 取消跳转
    }
  }
}

// Vue3 组件内守卫(setup语法)
import { onBeforeRouteEnter, onBeforeRouteLeave } from 'vue-router'

// 进入组件前拦截
onBeforeRouteEnter((to, from, next) => {
  const token = localStorage.getItem('token')
  if (!token) {
    next('/login')
  } else {
    next()
  }
})

// 离开组件前拦截
onBeforeRouteLeave((to, from, next) => {
  if (confirm('确定要离开吗?内容未保存')) {
    next()
  } else {
    next(false)
  }
})

五、路由跳转常见问题及解决方案

1. 跳转后页面不刷新

原因:路由参数变化(如从/user/1跳转到/user/2),组件会复用,不会重新触发mounted生命周期。

解决方案:监听路由变化,触发数据重新请求:

// Vue2 监听路由
watch: {
  '$route'(to, from) {
    // 路由变化时,重新请求数据
    this.getUserData(to.params.id)
  }
}

// Vue3 监听路由
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()

watch(
  () => route.params,
  (newParams) => {
    // 监听params变化,重新请求数据
    getUserData(newParams.id)
  },
  { deep: true }
)

2. params参数刷新后丢失

原因:params参数不暴露在URL上,页面刷新后,路由信息重置,参数丢失。

解决方案:1. 改用query参数(适合非敏感数据);2. 将params参数存储到localStorage/sessionStorage,刷新后重新读取。

3. 路由跳转后,URL正确但页面空白

常见原因:1. 路由配置错误(path拼写错误、component路径错误);2. 未在页面中添加<router-view>标签(路由出口,用于渲染跳转后的组件)。

解决方案:核对路由path和component路径,确保App.vue中包含<router-view>

<!-- App.vue 必须添加路由出口 -->
<template>
  <div id="app">
    <router-link to="/"&gt;首页&lt;/router-link&gt;
    &lt;router-view /&gt; <!-- 路由跳转后的组件会渲染在这里 -->
  </div>
</template>

4. Vue3中报错“Cannot read property 'push' of undefined”

原因:Vue3 setup语法中,未通过useRouter引入路由实例,直接使用this.$router(setup中无this)。

解决方案:正确引入useRouter,创建路由实例后再使用:

// 正确用法
import { useRouter } from 'vue-router'
const router = useRouter()
router.push('/about') // 无报错

六、总结

Vue路由跳转核心分为3种方式,结合场景选择即可:

  • 简单页面切换:用声明式跳转() ,简洁高效;
  • 需结合业务逻辑(登录、判断):用编程式跳转(router.push) ,灵活可控;
  • 自动跳转(默认页、404):用路由重定向(redirect) ,无需用户操作。

关键注意点:Vue2和Vue3的跳转语法差异主要在“是否使用this”,Vue3需通过useRouter/useRoute引入路由实例和路由对象;参数传递需区分query(刷新保留)和params(刷新丢失);权限控制用导航守卫,避免未授权访问。

本文所有示例均可直接复制到项目中使用,只需根据自身项目的路由配置,修改路径和组件名称即可快速适配。

❌
❌