Skip to content

路由进阶与项目收尾 ​

本页对应 路由进阶与项目收尾,共 6 段视频,约 1 小时 31 分。

学习目标 ​

  • 能够复用同一个抽屉组件区分添加、编辑、预览操作,并通过 type 与面经 id 控制打开逻辑。
  • 能够调用单个面经详情接口,将标题、内容、ID 等字段回填到表单,并验证编辑与预览时数据正确显示。
  • 能够在提交表单时根据操作类型或是否存在 ID 区分添加与修改,调用对应 API 完成新增或更新并给出正确提示。
  • 能够使用 v-if / v-else 在表单与预览内容之间切换,并通过可选链避免关闭无表单抽屉时重置表单报错。
  • 能够配置全局前置导航守卫,根据 token 判断登录状态并拦截未登录用户访问非登录页。
  • 能够执行项目打包,并通过 publicPath、关闭 source map、配置 CDN 与 externals 排除第三方包来减小产物体积。

知识点 ​

本节说明 ​

本节笔记主题是“声明式导航-导航链接”。你提供的转写稿主要讲面经项目的修改、预览、导航守卫和打包,与本节 router-link 知识点不直接对应,因此下面不强行套用无关转写内容,只依据笔记把本节讲清楚,避免编造。

需求:为什么不用普通 a 标签 ​

需求是实现导航高亮效果。

导航高亮效果示意图

如果使用普通 a 标签跳转,点击某个导航后,需要手动给当前 a 标签加高亮样式,同时还要移除上一个 a 标签的样式。页面一多、导航一多,这种“手动加类名、手动删类名”的写法就很麻烦,也容易漏掉状态。

怎么想: 导航高亮本质上有两个动作:一是跳转,二是根据当前路由自动标记哪个链接处于激活状态。普通 a 标签只能完成跳转,激活状态要自己维护;而 Vue Router 已经把这件事封装好了。

怎么做: 使用 Vue Router 提供的全局组件 router-link 取代普通 a 标签。

易错点: 不要把 router-link 当成普通链接后继续手写高亮逻辑;也不要漏掉路由出口 router-view,否则路径变了但组件没有展示位置。

vue-router 提供了一个全局组件 router-link,它可以取代 a 标签。

它有两个核心能力:

  • 能跳转:通过 to 属性指定路径,to 是必须的。它本质还是 a 标签,但 to 中无需写 #。
  • 能高亮:默认就会提供高亮类名,可以直接给这些类名设置高亮样式。

语法是:

vue
<router-link to="path的值">发现音乐</router-link>

完整示例代码保留如下:

vue
  <div>
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/friend">朋友</router-link>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>

为什么这样写: to="/find" 表示点击后跳转到 /find 路由;router-view 负责把匹配到的组件渲染出来。

怎么想: 把 router-link 理解为“带路由能力的 a 标签”。你仍然写导航文字,但跳转地址交给 to,激活状态交给 Vue Router。

易错点:

  1. to 写成 to="#/find"。笔记明确说 to 无需 #。
  2. to 的路径和路由配置不一致,例如路由配的是 /find,这里写成 /find/ 或 /finds。
  3. 只写了 router-link,忘记写 router-view,导致组件没有出口。
  4. 把 to 漏写或拼错,组件无法正确跳转。

高亮类名:利用默认类名设置样式 ​

使用 router-link 跳转后,当前点击的链接默认会加上两个 class 值:

  • router-link-exact-active
  • router-link-active

我们可以给任意一个 class 属性添加高亮样式,即可实现功能。

为什么需要: 如果不了解默认类名,就会误以为还要自己写 JS 判断当前路由。其实 Vue Router 已经根据当前路由自动加好了类名。

怎么想: 高亮样式不是加在 router-link 标签的行内样式上,而是写在对应激活类名的 CSS 选择器里。当前链接命中类名,就显示高亮。

怎么做: 在样式里针对 .router-link-active 或 .router-link-exact-active 设置颜色、下划线、背景等即可。笔记中只要求“给任意一个 class 属性添加高亮样式”,具体样式可按项目设计稿写。

易错点:

  1. 类名拼错,例如写成 router-link-activee。
  2. 在 scoped 样式中选择器写错,导致样式不生效。
  3. 不理解两个类名的区别:router-link-active 更偏向“匹配到该路由”;router-link-exact-active 更偏向“精确匹配当前路由”。实际项目按高亮范围选择即可。
  4. 以为必须两个类名都写样式,实际上给任意一个添加样式就能实现基本高亮。

总结:三个问题回顾 ​

  • router-link 是什么? 它是 Vue Router 提供的全局组件,用来替代 a 标签完成声明式导航。
  • router-link 怎么用? 写 <router-link to="路径">文字</router-link>,to 必须写,且无需 #;同时页面中要有 router-view 作为路由出口。
  • router-link 的好处是什么? 既能跳转,又能自动提供高亮类名,省去手动给当前链接加样式、移除上一个链接样式的麻烦。

学习这一节时,重点不是死记标签,而是理解“声明式导航”的思路:导航地址写在模板里,激活状态由路由自动维护。这样代码更简洁,也更不容易出错。

router-link自动添加两个类名示意图

为什么需要:做导航高亮时,如果每个菜单都手写“当前选中”的判断,代码会重复,而且容易和路由状态不同步。<router-link> 的价值就是:它根据当前地址自动给导航加上状态类名,我们只写样式即可。

怎么想:先不要急着写样式,先确认“当前导航”到底被加了哪些类名。笔记给出的结论是:使用 <router-link></router-link> 跳转时,Vue Router 会自动给当前导航加两个类名——router-link-active 和 router-link-exact-active。

怎么做:写一个导航链接,例如:

html
<router-link to="/my">我的</router-link>

然后在浏览器里检查它渲染后的元素类名。重点看两个类名是否出现,以及访问不同路径时它们的变化。

易错点:不要以为 router-link 只加一个类名;也不要手动再写一套“选中类名”逻辑,否则容易和自动类名冲突。调试时看渲染后的元素,不要只盯着模板里的 <router-link> 标签。

为什么需要:真实项目里常有二级、三级路由。例如访问 /my/a 时,左侧“我的”这个一级导航仍然应该高亮。如果只认 /my,父级导航就会丢失选中状态。

怎么想:router-link-active 的关键是“以 to 开头即可”。笔记写得很明确:to="/my" 可以匹配 /my、/my/a、/my/b……只要路径以 /my 开头,都能匹配到。

怎么做:把导航写成:

html
<router-link to="/my">我的</router-link>

当地址是 /my 时,它会匹配;当地址是 /my/a 或 /my/b 时,也仍然匹配。因此它适合做“父级菜单保持高亮”的样式。

易错点:router-link-active 是模糊匹配,不是“只有当前路径完全等于 to 才高亮”。如果你拿它做精确高亮,访问子路由时父级也会亮,可能不符合设计。另一个易错点是:看到 /my 高亮就以为它只能匹配 /my,忽略了子路径也会命中。

为什么需要:有些场景只希望“当前地址完全等于导航目标”时才高亮。例如首页导航,访问 /my/a 时不应该把“我的”当成完全相等;或者某些独立菜单需要严格区分。

怎么想:router-link-exact-active 的规则是精确匹配。笔记给出的例子是:to="/my" 仅可以匹配 /my。也就是说,/my/a 不会命中精确匹配类名。

怎么做:仍然使用同一个导航:

html
<router-link to="/my">我的</router-link>

访问 /my 时,router-link-exact-active 会出现;访问 /my/a 时,它不应再作为“完全相等”的当前项。样式上可以用它做“只有完全相等才高亮”的效果。

易错点:不要把 router-link-active 和 router-link-exact-active 混用。需要父级保持高亮时用前者;需要严格相等时用后者。还要注意:精确匹配不是“路径包含”,而是“完全对上”。

在地址栏输入二级路由观察类名变化 ​

为什么需要:只看文字规则很容易记混,实际调试一次最可靠。笔记第 3 点也明确要求“在地址栏中输入二级路由查看类名的添加”。

怎么想:把观察点放在“地址变化 → 类名变化”上。先访问 /my,再访问 /my/a 或 /my/b,对比同一个导航元素上两个类名的出现情况。

怎么做:打开开发者工具,选中当前 router-link 渲染出的导航元素,查看它的 class。按笔记规则应观察到:/my 时两个类名都能匹配;进入 /my/a 这类二级路径后,模糊匹配仍可能保留,精确匹配则不再表示“完全相等”。

易错点:转写稿里把“导航守卫”说成“导航首位”,这里纠正为“导航守卫”。导航守卫是路由跳转前后的拦截逻辑,和本节 router-link 的两个类名不是同一件事;不要因为都在路由配置附近就混为一谈。调试类名时也不要被无关的路由守卫逻辑干扰。

总结:两个类名怎么选 ​

为什么需要:最终要在项目里做导航高亮,必须知道什么时候用模糊、什么时候用精确。

怎么想:router-link-active 负责“以目标路径开头”的模糊匹配,适合父级导航、多级路由保持高亮;router-link-exact-active 负责“完全相等”的精确匹配,适合只允许当前项高亮。

怎么做:先写 <router-link to="/my">我的</router-link>,再用地址栏切换 /my、/my/a 观察类名,最后按设计选择对应类名写样式。

易错点:不要手写重复的选中类名;不要把模糊匹配误当精确匹配;不要忽略二级路由对父级高亮的影响。记住本节结论:router-link 会自动给当前导航添加两个类名,区别就在于模糊匹配与精确匹配。

说明:提供的转写稿主要记录项目实战中的修改、预览、导航守卫与打包,与本小节“router-link 自定义高亮类名”无直接对应,以下按本节笔记展开,不混入无关内容。

本节要解决的问题 ​

router-link 在匹配路由时会自动生成两个高亮类名,用来表示当前链接是否处于激活状态。默认类名比较长,直接写样式不够方便,也不利于项目里统一命名。笔记中的问题很明确:router-link 的两个高亮类名太长了,我们希望能定制怎么办?

路由默认高亮类名过长

为什么需要:默认类名长,样式选择器写起来啰嗦;项目里如果希望统一使用 active、exact-active 这类短类名,就需要修改默认行为。
怎么想:既然类名是 router-link 自动生成的,就不能只在标签上临时改,而应该从“创建路由对象”的源头配置。VueRouter 提供了两个配置项,分别对应两个高亮类名。
怎么做:在 new VueRouter({...}) 的配置对象中增加 linkActiveClass 和 linkExactActiveClass。
易错点:不要误以为要写在 <router-link> 标签上;这两个是创建路由实例时的配置项。

配置项与匹配逻辑 ​

我们可以在创建路由对象时,额外配置两个配置项即可:linkActiveClass 和 linkExactActiveClass。

js
const router = new VueRouter({
  routes: [...],
  linkActiveClass: "类名1",
  linkExactActiveClass: "类名2"
})

配置自定义高亮类名

这里要区分两个概念:linkActiveClass 对应“模糊匹配”的高亮类名,linkExactActiveClass 对应“精确匹配”的高亮类名。也就是说,一个链接在包含匹配时可能激活,在完全一致时才会精确激活。配置后,样式里应使用你自定义的类名,而不要再写默认的长类名。

为什么需要:只改一个类名不够,因为 router-link 本身有两个高亮状态。
怎么想:把“模糊匹配”和“精确匹配”分别映射到两个短类名上,样式职责更清楚。
怎么做:在路由配置中同时写出两个配置项,名字要与 VueRouter 的配置项完全一致。
易错点:linkActiveClass 和 linkExactActiveClass 拼写较长,注意大小写;不要把它们写到 routes 数组的某个路由对象里,它们是 VueRouter 实例级别的配置。

代码演示 ​

下面把两个配置项改成项目里更短的类名:

js
// 创建了一个路由对象
const router = new VueRouter({
  routes: [
    ...
  ], 
  linkActiveClass: 'active', // 配置模糊匹配的类名
  linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})

配置完成后,router-link 在匹配时就会使用 active 和 exact-active 作为高亮类名。此时 CSS 中可以写:

css
.active {
  /* 模糊匹配激活样式 */
}

.exact-active {
  /* 精确匹配激活样式 */
}

为什么需要:代码演示把抽象配置落到实际写法,便于直接套用。
怎么想:先确定两个短类名,再在创建路由对象时配置,最后在组件样式里使用短类名。
怎么做:保持 routes 不变,只在 new VueRouter 的参数对象中追加两个配置项。
易错点:配置后如果样式仍写默认长类名,就不会生效;自定义类名要自己补 CSS,VueRouter 只负责生成类名。

总结 ​

如何自定义 router-link 的两个高亮类名?答案是在创建路由对象时配置 linkActiveClass 和 linkExactActiveClass:前者对应模糊匹配类名,后者对应精确匹配类名。配置位置是 new VueRouter({ routes, linkActiveClass, linkExactActiveClass }),不是 router-link 标签属性。掌握这一点后,就可以把默认的长类名替换成项目里更简洁、更易维护的类名。

1. 目标与场景 ​

本节要解决的问题是:在跳转路由时把参数带到目标页面。比如在搜索页点击“热门搜索”里的某个词,跳转到详情页/搜索页后,页面需要知道“刚才点的是哪个词”,否则目标页只能显示固定内容,无法根据用户点击变化。

查询参数传参示意图

  • 为什么需要:路由跳转本身只负责“去哪个页面”,但业务常常还要带“带什么数据过去”。热门搜索词就是典型场景。
  • 怎么想:把参数放在 URL 的查询串里,形如 /search?keyword=黑马程序员,目标页再从当前路由信息中读取。
  • 怎么做:router-link 的 to 写完整路径和查询串;目标页用 $route.query.参数名 接收。
  • 易错点:笔记里写“$router.query.参数名”是不准确的。$router 是路由实例,主要用于跳转;当前路由参数要用 $route.query.参数名(route 单数)。

2. 查询参数传参的写法 ​

声明式导航使用 <router-link>。传参格式:

vue
<router-link to="/path?参数名=值"></router-link>

接收参数的固定思路:

js
this.$route.query.参数名
  • 为什么需要:查询参数适合“键值对”形式,URL 上能直接看见,刷新页面参数仍在。
  • 怎么想:先确定参数名,例如 keyword;再确定哪个链接跳转时带上它;最后在目标组件创建时读取。
  • 怎么做:在 created 中读取并保存到 data,供模板渲染。
  • 易错点:参数名要前后一致;query 取到的值通常是字符串;不要把 $route 写成 $router。

转写稿里反复体现的思路是:点击时先确定要带过去的数据,目标方法再用形参接收并据此请求详情。迁移到本节就是:点击热门搜索链接时把关键词放进 URL,搜索页读取关键词。按转写稿的排查习惯,调试时先看点击后 URL 有没有查询串,再看目标页接收的键名是否一致。

3. 代码演示 ​

App.vue:

vue
<template>
  <div id="app">
    <div class="link">
      <router-link to="/home">首页</router-link>
      <router-link to="/search">搜索页</router-link>
    </div>

    <router-view></router-view>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.link {
  height: 50px;
  line-height: 50px;
  background-color: #495150;
  display: flex;
  margin: -8px -8px 0 -8px;
  margin-bottom: 50px;
}
.link a {
  display: block;
  text-decoration: none;
  background-color: #ad2a26;
  width: 100px;
  text-align: center;
  margin-right: 5px;
  color: #fff;
  border-radius: 5px;
}
</style>

Home.vue(热门搜索链接需要把 to 写成带查询参数的形式):

vue
<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input type="text">
      <button>搜索一下</button>
    </div>
    <div class="hot-link">
      热门搜索:
      <router-link to="/search?keyword=黑马程序员">黑马程序员</router-link>
      <router-link to="/search?keyword=前端培训">前端培训</router-link>
      <router-link to="/search?keyword=如何成为前端大牛">如何成为前端大牛</router-link>
    </div>
  </div>
</template>

<script>
export default {
  name: 'FindMusic'
}
</script>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

Search.vue:

vue
<template>
  <div class="search">
    <p>搜索关键字: {{ keyword }}</p>
    <p>搜索结果: </p>
    <ul>
      <li>.............</li>
      <li>.............</li>
      <li>.............</li>
      <li>.............</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'MyFriend',
  data () {
    return {
      keyword: ''
    }
  },
  created () {
    // 在created中,获取路由参数
    this.keyword = this.$route.query.keyword
  }
}
</script>

<style>
.search {
  width: 400px;
  height: 240px;
  padding: 0 20px;
  margin: 0 auto;
  border: 2px solid #c4c7ce;
  border-radius: 5px;
}
</style>

router/index.js:

js
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
  routes: [
    { path: '/home', component: Home },
    { path: '/search', component: Search }
  ]
})

export default router

main.js:

js
...
import router from './router/index'
...
new Vue({
  render: h => h(App),
  router
}).$mount('#app')

4. 调试与易错点 ​

  • 点击热门链接后,先看地址栏是否变成 /search?keyword=...,这是“传参成功”的第一证据。
  • 在 Search.vue 的 created 中打印 this.$route.query,确认参数名和值。
  • 如果取不到,优先检查 $route 是否误写成 $router,以及 to 中参数名是否与接收名一致。
  • 查询参数适合少量、可公开的键值数据;如果参数很多或结构复杂,要再考虑其他传参方式。

说明:你提供的转写稿主要讲面经项目的修改、预览、导航守卫与打包,未出现本节“声明式导航-动态路由传参”的推导或调试过程;为避免编造,下面只依据本节笔记扩写,不把无关转写稿硬套进来。本节笔记未附图片。

动态路由传参:先理解它解决什么问题 ​

为什么需要:声明式导航用 <router-link> 跳转时,经常要把一个值带到目标页面,例如搜索页要把关键词带到 /search 页面。若只用固定路径 /search,目标组件不知道用户搜了什么。动态路由传参就是把“参数”写进路径本身,让 URL 既负责跳转,也负责携带一个核心参数。

怎么想:把动态路由理解成“路径里留了一个变量位”。配置 path: '/search/:words' 后,/search/前端 中的“前端”就会被路由系统识别为 words 的值。目标组件通过 $route.params.words 读取。

怎么做:先配置动态路由,再配置导航链接,最后在组件中接收参数。笔记中的配置代码如下:

js
const router = new VueRouter({
  routes: [
    ...,
    {
      path: '/search/:words',
      component: Search
    }
  ]
})

导航链接写成 to="/path/参数值",例如:

html
<router-link to="/search/前端">搜索前端</router-link>

对应页面组件接收参数:

js
this.$route.params.words

易错点:动态路由后面的参数可以随便起名,但要有语义,例如 :words 比 :a 更清楚;params 后面的参数名要和动态路由配置的参数保持一致,配置的是 :words,接收时就不能写成 $route.params.keyword。另外,动态路由必须在路由表里先配置好,不能只在 to 里写一个不存在的动态段。

查询参数传参与动态路由传参怎么选 ​

为什么需要:同样是传参,查询参数和动态路由适合的场景不同。查询参数适合传多个参数,动态路由更适合传单个、语义明确的参数。理解差异后,才不会所有场景都混用。

怎么想:查询参数把参数放在 ? 后面,用 & 连接多个键值;动态路由把参数放进路径层级里。前者更像“筛选条件”,后者更像“路径的一部分”。笔记给出的对比如下:

  1. 查询参数传参,比较适合传多个参数

    1. 跳转:to="/path?参数名=值&参数名2=值"
    2. 获取:$route.query.参数名
  2. 动态路由传参,优雅简洁,传单个参数比较方便

    1. 配置动态路由:path: "/path/:参数名"
    2. 跳转:to="/path/参数值"
    3. 获取:$route.params.参数名

怎么做:如果搜索页只有一个关键词,用动态路由:/search/前端,组件读 $route.params.words。如果要带关键词、页码、排序等多个条件,用查询参数:/search?keyword=前端&page=1,组件读 $route.query.keyword 和 $route.query.page。

易错点:不要把 query 和 params 混读;动态路由也可以传多个参数,但笔记特别提醒“一般只传一个”,因为路径会变长、可读性下降。还有一点:查询参数中的参数名不需要在路由表里提前占位,而动态路由必须在 path 中配置 :参数名。

总结:声明式导航跳转时的两种传值方式 ​

为什么需要:实际开发中,跳转传值是高频操作。把两种方式总结清楚,写代码时才能快速判断该用哪一种。

怎么想:先看参数数量和使用语义。多个筛选条件优先查询参数;单个核心参数优先动态路由。再看接收方式:查询参数读 $route.query,动态路由读 $route.params。

怎么做:声明式导航跳转时,有几种方式传值给路由页面?笔记总结为两种:

  • 查询参数传参,适合多个参数;
  • 动态路由传参,适合一个参数,优雅简洁。

易错点:动态路由传参时,参数名要和路由配置一致;查询参数传参时,多个参数用 & 连接;不要因为动态路由“看起来短”就强行把多个筛选条件都塞进路径。按笔记的结论选型即可。

问题现象:不传参数为什么显示空白 ​

笔记里先给出一个现象:路由配置为:

js
{ path: '/search/:words', component: Search }

按笔记中的操作访问时,如果没有传 words 参数,页面没有匹配到组件,所以显示空白。对应示意图如下:

路由未匹配显示空白示意图

为什么需要关注这个问题?
搜索页并不一定总是带着关键词进入。比如用户从菜单直接点“搜索”,此时地址可能只是 /search,但页面仍然应该能打开,而不是白屏。
怎么想?
空白并不一定是组件写错了,首先要判断路由是否匹配成功。/search/:words 中的 :words 是动态路由参数,默认表示这一段必须存在。地址里缺少这一段,路由规则自然匹配不上。
怎么做?
先确认地址是否确实缺少 words,再检查路由表中该参数的写法。
易错点: 不要把“组件没渲染”直接归因于组件内部逻辑;路由未匹配时,组件根本不会创建。

原因:动态参数默认是必填的 ​

笔记给出的原因很明确:/search/:words 表示必须要传参数。如果不传参数,也希望匹配,可以加可选符 ?。

为什么需要可选符?
因为业务上“搜索页存在”与“搜索关键词存在”是两件事。页面应该始终能进入,关键词可以有或没有。
怎么想?
把动态段理解为路径的一部分:/search/:words 要求地址里在 /search/ 后面还有一段内容;/search/:words? 则告诉路由:这一段可以省略。
怎么做?
在参数名后面加 ?,而不是在 /search 后面随便加问号。笔记中的写法是:

js
const router = new VueRouter({
  routes: [
    ...
    { path: '/search/:words?', component: Search }
  ]
})

易错点: ? 的位置要跟着动态参数走,写成 /search/:words?;不要和 URL 查询字符串混淆。/search?words=vue 是查询参数,/search/:words? 是路径参数可选,两者不是一回事。

匹配效果与组件取值 ​

加上可选符后,/search 和 /search/vue 都应能进入 Search 组件;前者没有 words,后者可以通过 $route.params.words 取到 vue。

为什么需要验证两种地址?
只看 /search/vue 能打开还不够,因为原写法本来就能匹配带参数的地址;真正要验证的是不带参数时是否也能进入页面。
怎么想?
可选符只解决“路由是否匹配”的问题,不解决组件内部取值问题。组件里仍然要考虑参数不存在的情况。
怎么做?
在组件中对参数做兜底,例如先判断再使用,避免直接对空值取长度或做字符串操作。
易错点: 不要认为加了 ? 后 $route.params.words 就一定是一个字符串;不传参数时它可能没有值,组件里要按空值处理。另一个易错点是只改路由表不验证页面,看到仍空白就继续改组件,实际上应先确认路由是否命中。

关于转写稿的说明 ​

本节转写稿主要讲的是数据回填、修改、预览、导航守卫和打包等内容,没有出现“动态路由参数可选符”的调试过程。因此这里不把无关片段硬套进本节,也不编造控制台输出;本节只依据笔记中的现象、原因和代码展开。实际排查时,按“地址是否缺少参数 → 路由 path 是否写了 :words? → 组件是否拿到参数并兜底”的顺序检查即可。

小结 ​

/search/:words 是必填动态参数,缺少参数时路由可能匹配失败并显示空白;如果希望不传参数也能进入页面,就在参数名后加可选符,写成 /search/:words?。这是一个“了解”级知识点,关键不是背语法,而是理解:页面是否需要存在和参数是否必须存在要分开设计。只有业务上确实允许参数缺省时,才加可选符;同时组件内部仍要对参数为空的情况做好兜底。

课件图示 ​

课件第2页:目录 Contents u 路由进阶 ① 路由模块封装 ② 声明式导航 & 导航

课件第18页:1. 查询参数传参 2. 动态路由传参 声明式导航 - 跳转传参 目标:在跳转路

课件第22页:总结 小结 声明式导航跳转时, 有几种方式传值给路由页面? ① 查询参数传参 (

课件第20页:2. 动态路由传参 ① 配置动态路由 ② 配置导航链接 l to="/path/

课件第48页:总结 总结 1. 项目案例实现的基本步骤分哪两大步? ① 配路由 ② 实现页面功

课件第19页:1. 查询参数传参 ① 语法格式如下 l to="/path?参数名=值" ②

课件第12页:① router-link-active 模糊匹配 (用的多) to="/my"

课件第15页:声明式导航 - 两个类名 说明:router-link 的 两个高亮类名 太长了

代码示例 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>01 router-link 声明式导航</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
  <style>
    .footer_wrap { display: flex; background: #495150; }
    .footer_wrap a { color: #fff; text-decoration: none; padding: 10px 20px; }

    /* 自定义类名后,默认的 router-link-active 就不再使用了 */
    .footer_wrap a.active       { background: #ad2a26; }  /* 模糊匹配 */
    .footer_wrap a.exact-active { font-weight: bold; }    /* 精确匹配 */
  </style>
</head>
<body>
  <div id="app">
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/friend">朋友</router-link>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>

  <script>
    const Find   = { template: `<div>发现音乐页面</div>` }
    const Friend = { template: `<div>朋友页面</div>` }
    // 二级路由:用于观察 router-link-active 的“模糊匹配”
    const My = {
      template: `
        <div>
          我的音乐页面
          <router-link to="/my/a">二级路由 a</router-link>
          <router-view></router-view>
        </div>
      `
    }
    const MyA = { template: `<div>我是 /my/a 页面</div>` }

    // 创建了一个路由对象
    const router = new VueRouter({
      routes: [
        { path: '/find',   component: Find },
        { path: '/friend', component: Friend },
        {
          path: '/my',
          component: My,
          children: [{ path: 'a', component: MyA }]
        }
      ],
      linkActiveClass: 'active',           // 配置模糊匹配的类名
      linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
    })

    new Vue({
      el: '#app',
      router
    })
  </script>
</body>
</html>

关键行与易错点

  • <router-link to="/find"> 本质仍是 a 标签,但 to 里不写 #,直接写 path 的值;to 属性是必须的。
  • <router-view> 是路由出口,匹配到的组件显示在这里,漏写就只跳转不显示内容。
  • 未自定义类名时,router-link 会自动加 router-link-active(模糊匹配)和 router-link-exact-active(精确匹配),给其中任意一个写样式即可高亮。
  • 一旦配置了 linkActiveClass / linkExactActiveClass,默认的两个长类名就不再生效,CSS 里必须写自定义的 .active / .exact-active,否则高亮"消失"。
  • 进入 /my/a 时,to="/my" 的链接仍然带 active(模糊匹配),但不带 exact-active(精确匹配),这是两者的核心区别。

代码 2:查询参数传参(?参数名=值) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>02 查询参数传参</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
  <style>
    .link {
      height: 50px;
      line-height: 50px;
      background-color: #495150;
      display: flex;
      margin: -8px -8px 0 -8px;
      margin-bottom: 50px;
    }
    .link a {
      display: block;
      text-decoration: none;
      background-color: #ad2a26;
      width: 100px;
      text-align: center;
      margin-right: 5px;
      color: #fff;
      border-radius: 5px;
    }
    .search-box { display: flex; justify-content: center; }
    .search-box input {
      width: 400px; height: 30px; line-height: 30px;
      border: 2px solid #c4c7ce; border-radius: 4px 0 0 4px; outline: none;
    }
    .search-box input:focus { border: 2px solid #ad2a26; }
    .search-box button {
      width: 100px; height: 36px; border: none;
      background-color: #ad2a26; color: #fff;
      position: relative; left: -2px; border-radius: 0 4px 4px 0;
    }
    .hot-link { width: 508px; height: 60px; line-height: 60px; margin: 0 auto; }
    .hot-link a { margin: 0 5px; }
    .search {
      width: 400px; height: 240px; padding: 0 20px; margin: 0 auto;
      border: 2px solid #c4c7ce; border-radius: 5px;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="link">
      <router-link to="/home">首页</router-link>
      <router-link to="/search">搜索页</router-link>
    </div>

    <router-view></router-view>
  </div>

  <script>
    const Home = {
      template: `
        <div class="home">
          <div class="search-box">
            <input type="text">
            <button>搜索一下</button>
          </div>
          <div class="hot-link">
            热门搜索:
            <router-link to="/search?key=黑马程序员">黑马程序员</router-link>
            <router-link to="/search?key=前端培训">前端培训</router-link>
            <router-link to="/search?key=如何成为前端大牛">如何成为前端大牛</router-link>
          </div>
        </div>
      `
    }

    const Search = {
      template: `
        <div class="search">
          <p>搜索关键字: {{ $route.query.key }}</p>
          <p>搜索结果: </p>
          <ul>
            <li>.............</li>
            <li>.............</li>
            <li>.............</li>
            <li>.............</li>
          </ul>
        </div>
      `,
      created () {
        // 在 created 中,获取路由参数
        console.log('接收到的关键字:', this.$route.query.key)
      }
    }

    // 创建了一个路由对象
    const router = new VueRouter({
      routes: [
        { path: '/home',   component: Home },
        { path: '/search', component: Search }
      ]
    })

    new Vue({
      el: '#app',
      router
    })
  </script>
</body>
</html>

关键行与易错点

  • 传参格式:to="/path?参数名=值",多个参数用 & 连接,如 /search?key=黑马&page=1。
  • 接收参数用 $route.query.参数名——注意是 $route(无 r),笔记里写的 $router.query 是笔误,$router 是路由实例(用于 push 等方法),拿不到参数。
  • 参数名要和 to 里写的完全一致,大小写敏感。
  • to="/search" 不带参数时也能正常进入搜索页,只是 $route.query.key 为 undefined,页面上就是空白,不会报错。
  • 在 created 里取值最稳妥,此时路由参数已就绪;写在 data 里直接赋值会因为时序问题取不到。

代码 3:动态路由传参(/path/:参数名)+ 可选符 ? ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>03 动态路由传参</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue-router@3/dist/vue-router.js"></script>
  <style>
    .link {
      height: 50px; line-height: 50px; background-color: #495150;
      display: flex; margin-bottom: 50px;
    }
    .link a {
      display: block; text-decoration: none; background-color: #ad2a26;
      width: 100px; text-align: center; margin-right: 5px;
      color: #fff; border-radius: 5px;
    }
    .hot-link { width: 508px; height: 60px; line-height: 60px; margin: 0 auto; }
    .hot-link a { margin: 0 5px; }
    .search {
      width: 400px; height: 240px; padding: 0 20px; margin: 0 auto;
      border: 2px solid #c4c7ce; border-radius: 5px;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="link">
      <router-link to="/home">首页</router-link>
      <router-link to="/search">搜索页(不传参)</router-link>
    </div>

    <router-view></router-view>
  </div>

  <script>
    const Home = {
      template: `
        <div class="home">
          <div class="hot-link">
            热门搜索:
            <router-link to="/search/黑马程序员">黑马程序员</router-link>
            <router-link to="/search/前端培训">前端培训</router-link>
            <router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
          </div>
        </div>
      `
    }

    const Search = {
      template: `
        <div class="search">
          <p>搜索关键字: {{ $route.params.words }}</p>
          <p>搜索结果: </p>
          <ul>
            <li>.............</li>
            <li>.............</li>
          </ul>
        </div>
      `,
      created () {
        console.log('动态路由参数:', this.$route.params.words)
      }
    }

    // 创建了一个路由对象
    const router = new VueRouter({
      routes: [
        { path: '/home', component: Home },
        // :words 后面的参数名可随便起名,但要有语义;加 ? 表示该参数可选
        { path: '/search/:words?', component: Search }
      ]
    })

    new Vue({
      el: '#app',
      router
    })
  </script>
</body>
</html>

关键行与易错点

  • 动态路由要分两步配对:路由表写 path: '/search/:words',跳转写 to="/search/参数值",接收写 $route.params.words,三处的参数名必须一致。
  • 取值用 $route.params,不是 $route.query;两套传参方式不能混着取。
  • 不加 ? 时,/search/:words 表示必须传参,直接访问 /search 会匹配不到组件、页面显示空白;写成 '/search/:words?' 加了可选符 ?,不传参也能匹配。
  • 这里可以删掉 ? 再点"搜索页(不传参)"验证一次,直观看到"未匹配到组件"的空白效果。
  • 两种传参方式选型:查询参数适合传多个参数(?a=1&b=2);动态路由更适合传单个参数,地址更优雅简洁。

常见错误 ​

  1. 导航点击后整页刷新 / 地址变成 /#/find 现象:点 router-link 页面白屏或跳转后高亮丢失,地址栏多出 #。 原因:沿用了 <a href="#/find"> 的写法,把 # 也写进了 to,或者干脆还在用 a 标签手动控制样式。 修法:to 只写路径本身(to="/find"),删掉 #;跳转交给 router-link,不要再手写 a + 手动切 class。

  2. 写了 .active { color: red } 但高亮不生效 现象:样式写好了,点击却没反应,F12 看不到自己写的类名。 原因:router-link 默认加的是 router-link-active / router-link-exact-active;如果路由里配了 linkActiveClass,默认类名就不存在了。 修法:先确认 new VueRouter({...}) 里有没有配自定义类名;配了就用自定义名,没配就用默认名,两边必须一致。

  3. $router.query.xxx 取到 undefined,控制台还报错 现象:传参成功但页面拿不到值。 原因:把 $router(路由器实例)和 $route(当前路由信息)混用了。 修法:取参数一律用 $route.query.参数名 / $route.params.参数名;$router 只用来做 push/replace/go 这类跳转动作。

  4. 动态路由参数拿不到,页面正常但 params.words 是空 现象:to="/search/黑马" 能跳,created 里打印 $route.params 是 {}。 原因:路由表里还写着 path: '/search',没有声明 :words,参数被当成路径的一部分丢掉了。 修法:path: '/search/:words';若允许不带参数访问,写成 '/search/:words?',否则不传参会匹配不到组件、显示空白。

  5. 编辑完一条数据,再点添加,输入框里还残留上一条的内容 现象:抽屉关掉再打开,表单不干净;或者提交时接口报「ID 不允许」。 原因:form 是同一个对象,关闭抽屉时没有重置,id、content 一直留着。 修法:关闭抽屉时手动把 form 恢复成初始结构(content: ''、stem: '' 并去掉 id),不要只依赖 resetFields;同时提交时用 type(或 form.id 是否存在)区分添加 / 修改,走不同接口。

  6. 预览抽屉一关就报 Cannot read properties of undefined (reading 'resetFields') 现象:编辑/添加关闭正常,预览关闭控制台红字。 原因:预览状态下 v-if 把表单整个销毁了,this.$refs.form 是 undefined,却直接调了方法。 修法:this.$refs.form?.resetFields(),用可选链先判断存在再调用;或先 if (this.$refs.form) 再执行。

  7. 导航守卫加了但没拦住,或者一进页面就死循环 现象:退出登录后仍能进列表页;或者疯狂跳登录页。 原因:next('/login') 之后没有 return,后面的 next() 又执行了一次;或者判断条件里没排除登录页本身。 修法:条件分支里 next('/login'); return,或者用 if / else 二选一;条件里务必带上 to.path !== '/login'。

  8. 打包后 dist/index.html 双击打开是白屏;或配了 CDN 报 ECharts is not defined 现象:本地 serve 正常,dist 打开空白 / 控制台报全局变量未定义。 原因:一是没配 publicPath: './',资源按绝对路径找;二是 externals 的 value 写成了包名,而它必须是 CDN 脚本挂到 window 上的那个全局变量名。 修法:vue.config.js 里加 publicPath: './';externals 写成 { echarts: 'echarts' } 这类「key=包名,value=window 上的变量名」,并在 public/index.html 里按顺序引入 CDN 的 CSS/JS。

练习 ​

  1. 现有路由表只有 { path: '/my', component: My },请在不改路由表的前提下,让 /my/a 也能让「我的音乐」这个链接高亮,并说明你用的是哪个类名、为什么不用另一个。

  2. 给搜索页做两种跳转:从首页热门搜索点「黑马程序员」跳到 /search/黑马程序员,从另一个入口带「关键字 + 页码」跳到搜索页。分别写出 router-link 的 to 和搜索页里取参的代码,并说明你为什么这么选。

  3. 抽屉组件里添加、编辑、预览共用一个 el-drawer。请写出:三个入口分别需要传什么给 openDrawer;openDrawer 内部什么条件下才发请求拿详情;以及为什么添加操作不能发这个请求。

  4. 复现并修复下面这段代码的问题,说明报错原因:

    js
    handleClose () {
      this.$refs.form.resetFields()
      this.visible = false
    }

    要求:修复后「预览关闭」和「编辑关闭」两种场景都不报错,且再次点添加时表单是空的。

  5. 项目打包后体积 13MB。请列出你会做的三步优化,并说明每一步分别改哪个文件、改完怎么验证效果(用什么命令 / 看什么产物)。

原视频对照 ​

#视频时长
1面经修改-数据回填23:27
2面经修改-完成修改14:18
3面经修改-调整代码格式03:29
4面经预览07:24
5加入导航守卫06:14
6打包36:37

小结 ​

  • router-link 的价值是「跳转 + 高亮」二合一,to 不带 #,高亮靠 router-link-active(模糊)和 router-link-exact-active(精确)两个类名,配了 linkActiveClass 就只认自定义名。
  • 传参只有两条路:query(?a=1&b=2,适合多参数)和 params(/path/:id,适合单参数、更优雅),取值分别用 $route.query 和 $route.params,动态路由不配 :参数名 就一定拿不到。
  • 详情页这类「同一组件、不同模式」的场景,核心是入口传 type + id,抽屉内部按 type 决定要不要请求详情,添加是唯一不发请求的分支。
  • 数据回填不需要逐个 v-model 赋值,利用双向绑定直接给 form 对象赋 content / stem / id 即可;用不上的字段(作者、时间)不要一股脑塞进来。
  • 抽屉关闭时的表单重置是必做项,resetFields 负责清校验红字,数据残留则要手动把 form 还原成初始结构,两件事都要做;组件可能不存在时用 ?. 兜底。
  • 添加和修改共用同一个表单和同一个「确认」按钮,所以提交方法里必须先判断再分流:添加走新增接口,修改走 put 更新接口,提示文案也随之切换。
  • 导航守卫的三件套 to / from / next 里,关键是 to.path 和本地 token 的组合判断,且 next('/login') 后必须 return,否则会二次执行。
  • 打包产出的 dist 才是交付物:publicPath: './' 解决双击打开,externals + CDN 把 ECharts、Element UI、富文本编辑器这些大块头挪出包外,体积能从十几 MB 降到几百 KB,--report 用来定位「谁最大」。

学员练习参考答案(简版)

  1. 用 router-link-active(模糊匹配),因为 /my/a 以 /my 开头,能匹配到;exact-active 只认 /my,二级路径下就失效了。
  2. 单参数走动态路由:to="/search/黑马程序员" + path: '/search/:words' + $route.params.words;多参数走 query:to="/search?keyword=xx&page=1" + $route.query.keyword。
  3. 添加传 ('add'),编辑传 ('editor', row.id),预览传 ('view', row.id);openDrawer 里 if (type !== 'add') 才 await getDetail(id);添加是全新数据,没有 id 也没有旧数据可回填。
  4. 预览模式下 v-if 销毁了表单,$refs.form 为 undefined;改为 this.$refs.form?.resetFields(),并补一句手动重置 form 对象(清 content/stem、去 id)。
  5. ①vue.config.js 加 publicPath: './' + productionSourceMap: false;②externals 排除 echarts / element-ui / 富文本编辑器,public/index.html 用 CDN 引入;③npm run build -- --report 看报告,确认 dist 体积和最大文件已换人。