Skip to content

路由切换时的钩子函数与路由导航亮度显示(Lesson10 2/3) ​

本页对应 Lesson10,共 5 段视频,约 1 小时 10 分。

学习目标 ​

  1. 能独立搭建 Vue Router 路由结构,完成路由表配置、路由模式设置、router-link 导航与 router-view 渲染,并验证页面切换正常。
  2. 能使用 query 方式传递和接收路由参数,正确读取 $route.query 中的字符串数据,并说明刷新后参数不丢失的特点。
  3. 能使用 params 配合 name 传递和接收路由参数,正确读取 $route.params,并说明刷新后参数丢失的原因。
  4. 能配置动态路由 path 参数(如 /detail/:id),通过 $route.params 接收参数,并实现从列表页跳转到详情页的路由应用。
  5. 能处理同一路由仅参数变化时的参数更新问题,使用 beforeRouteUpdate 或监听 $route 获取最新参数,并验证组件不会重新挂载时数据仍能更新。
  6. 能配置路由导航高亮样式(如 linkActiveClass 或精确匹配),验证当前路由链接正确显示激活状态。

知识点 ​

先看现象:路由切换时到底谁先谁后 ​

本节要解决的是一个很具体的问题:从 A 路由切换到 B 路由时,两个组件的生命周期钩子到底按什么顺序执行?笔记给出的结论是:B 路由会进行挂载,在 B 路由执行 mounted 钩子函数之前,销毁 A 路由。

为什么需要关心这个顺序?因为真实项目里,A 组件可能开了定时器、订阅了事件、发了请求,B 组件又要在挂载后操作 DOM 或继续请求数据。如果不知道谁先销毁、谁先挂载,就容易出现“旧组件还在跑定时器”“新组件拿不到 DOM”“请求结果回写到已销毁组件”等问题。

怎么想?不要靠猜,直接用生命周期日志把顺序打印出来。怎么做?在 Home.vue 和 NewsList.vue 中分别加入带编号的 console.log,然后从首页切换到新闻列表,观察控制台。易错点是:编号是全局观察顺序,不是每个组件内部独立编号;看到 8-Home->beforeDestroy 时,不要误以为它属于 NewsList。

怎么做:在两个路由组件中埋点 ​

下面保留本节笔记中的两个组件代码。Home.vue 负责输出 1—4 和 8—9,NewsList.vue 负责输出 5—7 和 10。

src/views/Home.vue:

vue
<template>
    <div>
        <h3>首页界面</h3>
    </div>
</template>

<script>
export default {
    name: "Home",
    beforeCreate() {
        console.log("1-Home->beforeCreate");
    },
    created(){
        console.log("2-Home->created");
    },
    beforeMount() {
        console.log("3-Home->beforeMount");
    },
    mounted(){
        console.log("4-Home->mounted");
    },
    beforeDestroy() {
        console.log("8-Home->beforeDestroy");
    },
    destroyed() {
        console.log("9-Home->destroyed");
    }
}
</script>

<style scoped>

</style>

src/views/NewsList.vue:

vue
<template>
    <div>
        <h3>新闻列表界面</h3>
    </div>
</template>

<script>
export default {
    name: "NewsList.vue",
    beforeCreate() {
        console.log("5-NewsList->beforeCreate");
    },
    created(){
        console.log("6-NewsList->created");
    },
    beforeMount() {
        console.log("7-NewsList->beforeMount");
    },
    mounted(){
        console.log("10-NewsList->mounted")
    },
    beforeDestroy() {
        console.log("NewsList->beforeDestroy");
    },
    destroyed() {
        console.log("NewsList->destroyed");
    }
}
</script>

<style scoped>

</style>

为什么这样埋点?因为只打印“进入了 Home”或“进入了 NewsList”看不出切换细节,而 beforeCreate → created → beforeMount → mounted 能看出新组件创建到挂载的过程,beforeDestroy → destroyed 能看出旧组件销毁的过程。怎么想?把编号按预期顺序排好,运行时对照控制台。怎么做?先刷新首页,应看到 1—4;再点击新闻列表,应看到 5—10。易错点是:不要在 mounted 里假设旧组件一定已经销毁,实际要以日志顺序为准。

运行后的预期顺序与解释 ​

从首页切到新闻列表,控制台预期顺序是:

text
1-Home->beforeCreate
2-Home->created
3-Home->beforeMount
4-Home->mounted

5-NewsList->beforeCreate
6-NewsList->created
7-NewsList->beforeMount
8-Home->beforeDestroy
9-Home->destroyed
10-NewsList->mounted

为什么需要看这个顺序?因为它直接决定清理逻辑放在哪里。怎么想?前四步说明 Home 已经完成挂载;切换后,NewsList 先走 beforeCreate、created、beforeMount,说明 B 路由已经开始创建和准备挂载;接着 Home 走 beforeDestroy、destroyed;最后 NewsList 才执行 mounted。这正好对应笔记结论:B 路由执行 mounted 之前,A 路由被销毁。

怎么做清理?如果 Home 里有定时器、事件监听、订阅,应该在 beforeDestroy 中清理,而不是等 destroyed 之后才想起来。易错点是:不要以为 A 的 destroyed 一定在 B 的 mounted 之后;本节日志明确显示它在 B 的 mounted 之前完成。转写稿里把 mounted 说成“猫体的”、把 updated 说成“off的”,这里统一纠正为 mounted、updated。

易错点:同一路由组件复用,不会重新销毁和挂载 ​

转写稿里补充了一个很关键的调试现象:如果只是查询字符串不同,比如从 /one?id=1 切到 /one?id=2,路由组件不会重新挂载,mounted 不会再次执行。也就是说,不能把“接收最新参数”的代码只写在 mounted 里。转写稿中实际观察到:参数变化时 updated 会执行;如果 $route 相关属性没有变化,updated 也可能不执行,此时可以用 watch 监听 $route.query。

为什么需要知道这一点?因为详情页里“从列表进详情”会触发 mounted,但在详情页内部再点另一个详情链接时,组件可能被复用,mounted 不再执行。怎么想?把“组件创建/销毁”和“参数变化”分开看。怎么做?同一路由参数变化时,用 watch 监听 $route.query 或 $route.params,或者用 updated 观察变化;不要依赖 mounted 重复执行。易错点是:以为地址变了组件就一定销毁重建;实际上同一路由组件复用时不走 beforeDestroy/destroyed,也不重新走 mounted。

调试与清理建议 ​

为什么需要调试?因为生命周期顺序在纸面上容易记混,实际日志最可靠。怎么想?先用编号日志确认顺序,再把清理逻辑放到 beforeDestroy,把依赖最新参数的逻辑放到 watch 或 updated。怎么做?刷新首页看 1—4;点新闻列表看 5—10;再测试同一路由仅参数变化的情况,确认 mounted 是否执行。易错点是:不要把 console.log 编号当成组件内部固定编号;不要在同一路由参数变化时期待旧组件销毁、新组件挂载;清理定时器和事件监听要在组件销毁前完成。

一、先纠正一个词:导航“亮度显示”应为“高亮显示” ​

为什么需要: 用户点击导航后,要知道自己当前在哪个页面;如果所有链接长得一样,就失去位置感。
怎么想: router-link 最终会渲染成链接,路由匹配成功时 Vue Router 会自动加类名,我们只要给类名写样式即可。
怎么做: 可以用内置类名、组件属性、全局配置三种方案。转写稿里“传舱”应为“传参”,“will”应为“Vue”,“rootot”应为“router”,本节先聚焦视频1中演示的导航高亮。
易错点: 高亮类名不是手写的 active,而是路由自动加的;不要把它和“组件是否重新挂载”混为一谈。

二、方案一:直接用内置类名写样式 ​

为什么需要: 不想改 router-link 属性时,直接写内置类名最省事。
怎么想: router-link-active 表示“只要匹配上即会使用该样式”;router-link-exact-active 表示“精确匹配即会使用该样式”。
怎么做: 在样式表里写:

css
.router-link-active {
  /* 只要匹配上即会使用该样式 */
}

.router-link-exact-active {
  /* 精确匹配即会使用该样式 */
}

易错点: 两个类名可能同时存在;如果首页路径是 /,访问 /one 时首页也可能被“匹配上”,所以会出现“首页被选中”的现象。

三、方案二:自定义样式类名 ​

为什么需要: 内置类名较长,项目里通常想统一写 .active。
怎么想: 样式写成 .active,再在 router-link 上告诉路由:激活时用哪个类名。
怎么做: 保留笔记中的三种写法:

html
<!-- 只要匹配上即会使用active样式 -->
<router-link active-class='active'></router-link>

<!-- 只有精确匹配才会使用 active 样式 -->
<router-link exact active-class='active'></router-link>

<!-- 只有精确匹配才会使用 active 样式 -->
<router-link exact-active-class='active'></router-link>

易错点: exact 是布尔属性,exact-active-class 是指定“精确匹配类名”;只写 active-class 仍然是“匹配上就激活”,不是精确匹配。

四、方案三:配置路由信息,统一设置 ​

为什么需要: 链接很多时,一个个写 active-class 很重复,适合全局统一。
怎么想: 在创建路由实例时配置默认类名,所有 router-link 共用。
怎么做: 保留笔记代码:

js
new VueRouter({
  // 只要匹配上路由即可使用该样式
  linkActiveClass: "active",
  // 只有精确匹配才会使用 active 样式
  linkExactActiveClass: "active"
})

易错点: 两个配置名不同:linkActiveClass 对应“匹配上”,linkExactActiveClass 对应“精确匹配”;配置后组件属性仍可覆盖全局配置。

五、结合转写稿调试:首页为什么会被选中 ​

转写稿视频1里先搭了 home、one、two、three 几个路由,在 APP 中写 router-link 跳到首页、one、two、three,并配置 linkActiveClass:"active"。调试时点击 one、two、three 正常,点击首页发现“首页被选中”。如果希望首页只在精确访问首页时高亮,转写稿给了两种改法:一是把路由配置改成 linkExactActiveClass,二是在首页的 router-link 上加 exact。
为什么需要: 根路径 / 很容易成为其他路径的“匹配前缀”,所以非精确匹配会误亮。
怎么想: 先判断你要“只要匹配上就亮”还是“必须完全一致才亮”。
怎么做: 全局改 linkExactActiveClass,或局部给首页链接加 exact。
易错点: 转写稿后面讲 query 时提到,/one?ID=1 切到 /one?ID=2 时组件不会重新挂载,但导航高亮判断仍看路由匹配;不要把“高亮变化”和“组件重新执行 mounted”混在一起。

六、三种方案怎么选 ​

为什么需要: 三种都能实现,选错会增加维护成本。
怎么想: 项目统一风格用方案三;单个链接特殊处理用方案二;临时演示或不想改属性用方案一。
怎么做: 先写全局 linkActiveClass,遇到首页等需要精确匹配的链接,再局部加 exact 或 exact-active-class。
易错点: 类名冲突、精确匹配漏加、把“亮度”写成“高亮”的口误带进代码注释,都是常见问题。

1. 路由传参为什么是重点 ​

做项目时,路由传参几乎避不开。比如医院列表点击某家医院进入详情页,必须把医院标识传给详情页,详情页再根据标识请求详情数据。本节围绕 Vue Router 的三种常用传参方式展开:query、params(配合 name)、params(配置 path)。传参只需要想清两件事:怎么传、怎么接收。

先搭一套最小路由。router/index.js 中引入组件并配置:

js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import One from '../views/One.vue'
import Two from '../views/Two.vue'
import Three from '../views/Three.vue'

Vue.use(VueRouter)

const routes = [
  { path: '/', name: 'home', component: Home },
  { path: '/one', name: 'one', component: One },
  { path: '/two', name: 'two', component: Two },
  { path: '/three/:a/:b', name: 'three', component: Three }
]

const router = new VueRouter({
  mode: 'history',
  routes,
  linkActiveClass: 'active'
})

export default router

App.vue 中用 router-link 和 router-view:

html
<router-link to="/">首页</router-link>
<router-link to="/one?id=1&type=2">one</router-link>
<router-link :to="{ name: 'two', params: { a: 1, b: 2 } }">two</router-link>
<router-link to="/three/1/2">three</router-link>
<router-view />

易错点:首页被高亮是因为 / 会匹配所有以 / 开头的地址,可给首页链接加 exact,或配置精确高亮类。转写稿里调试“点击 one、two、three、首页”就是在确认路由和高亮是否正常。

2. query 传参:地址栏查询字符串 ​

为什么需要:query 适合把参数放在地址栏,刷新不丢失,便于分享和统计。
怎么想:传参就是拼查询字符串,接收时从 this.$route.query 取。
怎么做:

html
<router-link to="/one?id=1&type=2">one</router-link>
<router-link :to="{ path: '/one', query: { id: 21, type: 22 } }">one3</router-link>

One.vue 接收:

js
mounted() {
  console.log(this.$route.query.id, this.$route.query.type)
}

转写稿调试时点击 one,地址出现 ?id=1&type=2,页面能取到 1 和 2;把值改成 20,type 也跟着变。query 对象写法会把对象转成查询字符串再拼到 path 后面。

易错点:query 接收到的值是字符串;如果传对象,地址里会出现被编码的 [object Object] 之类内容,使用前要转换。刷新后 query 数据还在,这是它的优点;但传递复杂数据不方便。

3. params 配合 name:数据在内存中 ​

为什么需要:不想把参数暴露在地址栏时,可用 name + params。
怎么想:to 写成对象,用 name 指定路由,用 params 带数据。
怎么做:

html
<router-link :to="{ name: 'two', params: { a: 1, b: 2 } }">two</router-link>

Two.vue:

js
mounted() {
  console.log(this.$route.params.a, this.$route.params.b)
}

转写稿里一开始在 to 的 path 上写 params 没效果,改成 name: 'two' 后才有数据。说明这种写法必须和 name 结合。

易错点:刷新会丢失,因为数据没有保存在地址栏。它可传对象、可保留类型,但刷新后取不到,适合临时跳转,不适合需要刷新仍可用的详情页。

4. params 配置 path:地址参数刷新不丢 ​

为什么需要:详情页常需要 /detail/xxx 这种地址,刷新后仍能根据地址取参数。
怎么想:在路由配置里用 :参数名 占位,接收仍从 this.$route.params 取。
怎么做:

js
{ path: '/three/:a/:b', name: 'three', component: Three }
html
<router-link to="/three/1/2">three</router-link>

Three.vue:

js
mounted() {
  console.log(this.$route.params.a, this.$route.params.b)
}

转写稿中把 /three/1/2 的一、二分别传给 a、b,刷新后数据仍在。也可以给地址加 .html,如 /three/1/2.html,配置时相应调整;这属于伪静态写法,常用于利于收录。

易错点:mounted 只执行一次。若当前已在 /one?id=1,再跳到 /one?id=2,路由组件不会重新挂载,mounted 不再执行。此时要用 watch 监听 $route.query,或使用 updated。转写稿调试“点 one2 没执行、点 one 才执行”就是在说明这一点。

5. 路由应用:列表进详情 ​

为什么需要:把传参落到真实场景。
怎么想:首页请求用户列表,点击用户名跳到详情,详情根据用户名请求详情接口。
怎么做:列表项可写:

html
<router-link :to="`/detail/${item.login}`"> {{ item.login }} </router-link>

详情路由配置 path: '/detail/:login',详情组件中:

js
mounted() {
  this.$axios.get('/users/' + this.$route.params.login).then(res => {
    this.user = res.data
  })
}

易错点:用 path 传参时,to 要拼完整地址;用 name 传参时,路由要有对应 name。从列表进详情可用 mounted,但在详情页内从一个用户跳到另一个用户时,组件可能复用,需监听 $route.params 变化再请求。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>路由切换生命周期</title>
</head>
<body>
  <div id="app">
    <p>
      <router-link to="/home">Home</router-link> |
      <router-link to="/news">NewsList</router-link>
    </p>
    <router-view></router-view>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.js"></script>
  <script>
    Vue.use(VueRouter);

    const Home = {
      name: "Home",
      template: `<div><h3>首页界面</h3></div>`,
      beforeCreate() {
        console.log("1-Home->beforeCreate");
      },
      created() {
        console.log("2-Home->created");
      },
      beforeMount() {
        console.log("3-Home->beforeMount");
      },
      mounted() {
        console.log("4-Home->mounted");
      },
      beforeDestroy() {
        console.log("8-Home->beforeDestroy");
      },
      destroyed() {
        console.log("9-Home->destroyed");
      }
    };

    const NewsList = {
      name: "NewsList",
      template: `<div><h3>新闻列表界面</h3></div>`,
      beforeCreate() {
        console.log("5-NewsList->beforeCreate");
      },
      created() {
        console.log("6-NewsList->created");
      },
      beforeMount() {
        console.log("7-NewsList->beforeMount");
      },
      mounted() {
        console.log("10-NewsList->mounted");
      },
      beforeDestroy() {
        console.log("NewsList->beforeDestroy");
      },
      destroyed() {
        console.log("NewsList->destroyed");
      }
    };

    const router = new VueRouter({
      routes: [
        { path: "/", redirect: "/home" },
        { path: "/home", component: Home },
        { path: "/news", component: NewsList }
      ]
    });

    new Vue({
      router
    }).$mount("#app");
  </script>
</body>
</html>

要点解释:

  • 从 Home 切换到 NewsList 时,按笔记顺序观察控制台:5 -> 6 -> 7 -> 8 -> 9 -> 10。
  • 关键点:B 路由先执行 beforeCreate / created / beforeMount,A 路由执行 beforeDestroy / destroyed,最后 B 路由执行 mounted。
  • 易错点:不要误以为“先销毁 A,再创建 B”;实际是 B 挂载前销毁 A,B 的 mounted 在 A 销毁之后执行。
  • 初次进入 /home 会输出 1 -> 2 -> 3 -> 4,可用于对比路由切换时的输出。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>路由导航高亮</title>
  <style>
    .active {
      color: blue;
    }

    .active-exact {
      color: red;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <div id="app">
    <p>
      <router-link to="/" exact>首页</router-link> |
      <router-link to="/one">One</router-link> |
      <router-link to="/two">Two</router-link> |
      <router-link to="/three">Three</router-link>
    </p>
    <router-view></router-view>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.js"></script>
  <script>
    Vue.use(VueRouter);

    const Home = { template: `<h3>首页界面</h3>` };
    const One = { template: `<h3>One 页面</h3>` };
    const Two = { template: `<h3>Two 页面</h3>` };
    const Three = { template: `<h3>Three 页面</h3>` };

    const router = new VueRouter({
      // 方案三:配置路由信息
      // 只要匹配上路由即可使用该样式
      linkActiveClass: "active",
      // 只有精确匹配才会使用 active 样式
      linkExactActiveClass: "active-exact",
      routes: [
        { path: "/", component: Home },
        { path: "/one", component: One },
        { path: "/two", component: Two },
        { path: "/three", component: Three }
      ]
    });

    new Vue({
      router
    }).$mount("#app");
  </script>
</body>
</html>

要点解释:

  • 方案一:默认类名是 .router-link-active 与 .router-link-exact-active;匹配上即高亮,精确匹配才高亮。
  • 方案二:可在 router-link 上写 active-class="active"、exact-active-class="active",只影响当前链接。
  • 方案三:在 new VueRouter({ linkActiveClass, linkExactActiveClass }) 中全局配置,本示例使用该方案。
  • 易错点:根路径 / 不加 exact 时,访问其他页面也可能匹配到首页,导致首页一直高亮。
  • 易错点:active-class 与 exact-active-class 是 router-link 组件属性;linkActiveClass 与 linkExactActiveClass 是路由构造函数配置。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>路由传参 query 与 params</title>
</head>
<body>
  <div id="app">
    <router-view></router-view>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.js"></script>
  <script>
    Vue.use(VueRouter);

    const Home = {
      template: `
        <div>
          <h3>首页界面</h3>
          <p>
            <router-link to="/one?id=1&type=2">
              query 字符串传参
            </router-link>
          </p>
          <p>
            <router-link :to="{ path: '/one', query: { id: 21, type: 22 } }">
              query 对象传参
            </router-link>
          </p>
          <p>
            <router-link :to="{ name: 'two', params: { a: 1, b: 2 } }">
              params + name 传参
            </router-link>
          </p>
          <p>
            <router-link :to="{ name: 'three', params: { a: 100, b: 200 } }">
              params + path 配置传参
            </router-link>
          </p>
          <p>
            <router-link to="/three/1/2">
              path 拼接传参
            </router-link>
          </p>
        </div>
      `
    };

    const One = {
      template: `
        <div>
          <h3>One 页面</h3>
          <p>id = {{ $route.query.id }}</p>
          <p>type = {{ $route.query.type }}</p>
        </div>
      `,
      mounted() {
        console.log("One mounted");
      },
      watch: {
        "$route.query"(to) {
          console.log("query 发生变化:", to);
        }
      }
    };

    const Two = {
      template: `
        <div>
          <h3>Two 页面</h3>
          <p>a = {{ $route.params.a }}</p>
          <p>b = {{ $route.params.b }}</p>
        </div>
      `
    };

    const Three = {
      template: `
        <div>
          <h3>Three 页面</h3>
          <p>a = {{ $route.params.a }}</p>
          <p>b = {{ $route.params.b }}</p>
        </div>
      `
    };

    const router = new VueRouter({
      routes: [
        { path: "/", component: Home },
        { path: "/one", component: One },
        { path: "/two", name: "two", component: Two },
        { path: "/three/:a/:b", name: "three", component: Three }
      ]
    });

    new Vue({
      router
    }).$mount("#app");
  </script>
</body>
</html>

要点解释:

  • query 传参:地址形如 /one?id=1&type=2,通过 this.$route.query.id 接收;接收到的通常是字符串。
  • query 对象写法:{ path: '/one', query: { id: 21, type: 22 } },Vue Router 会把对象转成查询字符串并与 path 拼接。
  • params + name:{ name: 'two', params: { a: 1, b: 2 } },通过 this.$route.params 接收;路由未在 path 中声明参数时,刷新会丢失。
  • params + path 配置:路由写成 /three/:a/:b,可用 name + params 或 /three/1/2 拼接,刷新后参数仍保留。
  • 易错点:同一路由只是 query 不同,组件不会重新挂载,mounted 不会再次执行;需要监听 $route.query 或在路由更新钩子中处理。
  • 易错点:params 传参要结合 name 或配置了动态参数的 path;不要误以为任意 path 都能携带 params 且刷新保留。

常见错误 ​

1. 详情页从 /detail/1 跳到 /detail/2,页面内容不变

  • 现象:URL 变了,mounted 里打的日志不再输出,页面还是上一个人的数据。
  • 原因:同一路由记录只换了参数,组件实例被复用,不会重新创建/挂载。
  • 修法:在组件里 watch: { '$route'(to){ this.load(to.params.id) } },或用 beforeRouteUpdate;应急方案是给 <router-view :key="$route.fullPath">(注意会牺牲复用)。

2. params 传参刷新后 undefined,接口 404

  • 现象:点击能进详情页,F5 之后 this.$route.params.id 为空。
  • 原因:参数只存在内存里,路由 path 中没有声明动态段,URL 上根本没带这个值。
  • 修法:路由写成 path: '/detail/:id',跳转用 { name:'detail', params:{ id } };若本来就要刷新保持,直接用 path 参数或 query。

3. query 取到的数字变成字符串,比较/计算全错

  • 现象:this.$route.query.id === 1 为 false;页码 page + 1 得到 "21"。
  • 原因:query 走 URL 文本,取出来一律是 string。
  • 修法:接收处显式转换 Number(...) / parseInt(...),并在组件内统一成数字再使用。

4. this.$route.push() 报错,或 this.$router.query 取不到值

  • 现象:Cannot read property 'push' of undefined;打印 query 为 undefined。
  • 原因:$router 是路由实例(负责跳转),$route 是当前路由信息(负责取值),两者用反了。
  • 修法:跳转 this.$router.push/replace,取参 this.$route.query / params / meta。

5. 导航高亮范围不对:首页永远亮,或详情页把列表项也点亮

  • 现象:访问 / 时多个链接同时高亮;访问 /news/1 时 /news 也高亮。
  • 原因:默认 router-link-active 是包含匹配,只要前缀匹配上就生效。
  • 修法:需要精确匹配的加 exact 或使用 exact-active-class;全局则在 new VueRouter({ linkActiveClass, linkExactActiveClass }) 里统一。

6. 写了 .active 样式却不高亮

  • 现象:CSS 里 .active { color: red } 完全没反应。
  • 原因:router-link 默认渲染的类名是 router-link-active,没有被告知使用 active。
  • 修法:<router-link active-class="active">,精确匹配用 exact-active-class="active";全局配置 linkActiveClass: 'active'。

7. path 与 params 同时写,参数丢失

  • 现象:this.$router.push({ path:'/detail', params:{ id:1 } }),到页面后 params 为空。
  • 原因:path 写法会忽略 params,params 只与 name 配合才生效。
  • 修法:改成 { name:'detail', params:{ id } },或把参数拼进 path('/detail/' + id),或改用 query。

8. 在 beforeDestroy 里操作新页面的 DOM 或数据

  • 现象:切路由时在旧组件销毁钩子里拿新页面元素,拿到 null 或旧值。
  • 原因:切换顺序是新组件 beforeCreate → created → beforeMount,随后旧组件 beforeDestroy → destroyed,最后新组件 mounted;旧组件销毁时新组件尚未挂载完成。
  • 修法:需要新 DOM 的操作放新组件 mounted;旧组件的清理(定时器、事件监听、订阅)才放 beforeDestroy,且不要依赖钩子顺序做跨组件通信。

练习 ​

练习 1 从 /list 点进 /detail/3,详情页有"下一条"按钮跳到 /detail/4,要求内容跟着变。 请写出至少两种实现方式,并说明为什么只写 mounted 不行。

参考:① watch: { '$route'(to){ this.fetch(to.params.id) } };② beforeRouteUpdate(to, from, next){ this.fetch(to.params.id); next() }。因为组件被复用、不重新挂载,mounted 只执行一次。

练习 2 实现搜索页 /search?keyword=vue&page=2,要求:(a) 刷新后搜索条件还在;(b) 在输入框改关键词回车后列表刷新;(c) 页码在页面上是数字类型。 请分别说明三点各自的关键做法。

参考:(a) 用 query,条件在 URL 上;(b) 回车时 this.$router.push({ path:'/search', query:{ keyword, page:1 } }),列表刷新靠 watch '$route';(c) 接收处 Number(this.$route.query.page)。

练习 3 顶部导航三项:首页 /、新闻 /news、新闻详情 /news/:id。 要求:首页只在 / 时高亮,新闻在 /news 和 /news/1 时都高亮。给出 router-link 写法(或全局配置方案)。

参考:首页 <router-link to="/" exact-active-class="active">(或加 exact);新闻 <router-link to="/news" active-class="active">。全局方案:linkActiveClass:'active' + linkExactActiveClass:'active'。

练习 4 某同学写 this.$router.push({ path:'/detail', params:{ id:1 } }),详情页拿不到 id。 指出问题,并给出两种修正写法(一种保持 params 语义,一种改用 URL 参数)。

参考:path 会忽略 params。修正一:{ name:'detail', params:{ id:1 } }(路由需有 name);修正二:{ path:'/detail/1' } 或 { path:'/detail', query:{ id:1 } }。

练习 5 商品列表页里有轮播定时器,切走再切回后发现定时器越叠越多,轮播速度越来越快。 请分析原因,并分别给出"不使用 keep-alive"和"使用 keep-alive"两种情况下的修法。

参考:组件每次进入都 setInterval 而没有清理。不用 keep-alive:beforeDestroy 里 clearInterval。用 keep-alive:组件不销毁,需用 activated 启动、deactivated 停止,并在 beforeDestroy 兜底清理。


原视频对照 ​

#视频时长
1搭建路由06:53
2query传递参数19:21
3params传递参数05:27
4params传递参数设置path11:24
5路由应用26:53

小结 ​

  1. 路由切换时,新组件的 created 早于旧组件的 beforeDestroy,但新组件的 mounted 晚于旧组件的 destroyed;操作新 DOM 用 mounted,清理旧资源用 beforeDestroy。
  2. 同一路由记录、仅参数变化时组件被复用,mounted 只执行一次,参数响应必须靠 watch $route 或 beforeRouteUpdate。
  3. query 参数在 URL 上,刷新不丢,但全是字符串、不适合传对象;name + params 能传任意类型,但刷新即失。
  4. 想让 params 刷新不丢,必须在 path 里声明 :id,此时参数本质来自 URL,同样是字符串,需要显式转换类型。
  5. $router 负责跳转,$route 负责取值,两者混用是最常见的低级错误。
  6. 导航高亮有两套类名(包含匹配 / 精确匹配),通过 active-class、exact-active-class 或全局 linkActiveClass、linkExactActiveClass 统一风格。
  7. 选型原则:需要分享链接、刷新保持、可收藏 → path 参数或 query;仅在页面间临时传递复杂对象 → name + params,并做好参数缺失的兜底。
  8. 地址后缀 .html 只是伪静态,对搜索引擎收录有轻微优先级帮助,与数据获取方式无关,别把它当成路由机制的一部分。