Skip to content

路由进阶(二) ​

本页对应 路由进阶,共 7 段视频,约 53 分钟。

学习目标 ​

  • 能够说明查询参数与动态路由传参在 URL 格式、路由规则和参数获取方式上的差异,并在代码中正确选择。
  • 能够使用 router-link 的 to 或编程式导航(path/name)完成带查询参数或动态参数的跳转,并在目标组件通过 $route.query / $route.params 获取参数。
  • 能够根据动态路由传参需求修改路由规则(含可选参数 ?),并验证地址与规则匹配、组件正常渲染。
  • 能够配置重定向规则,将根路径或指定路径重定向到目标路由,并验证打开页面不再空白。
  • 能够切换 hash/history 路由模式,说明两者 URL 差异并验证切换后路由跳转与参数传递正常。
  • 能够为未匹配路由配置 404 页面,并结合面经案例效果分析说明路由传参在详情页复用中的用途。

知识点 ​

404 路由要解决什么问题 ​

为什么需要:路由的本质,是地址变化后拿地址去和路由规则逐条匹配,匹配到哪条规则,就在路由出口渲染对应组件。转写稿里调试动态路由时已经出现过这个现象:地址改成 discover/toplist/... 后,如果没有任何规则和它一致,下面区域就一片空白,因为“没有相同的规则,所以它也不知道该展示谁”。用户手动输入错误地址、点错链接、旧链接失效时,也会出现这种空白。404 页面就是给“找不到匹配”的情况一个明确提示,而不是让用户面对空白。

怎么想:不要为每一个错误地址都写一条规则,那不可能写完。应该想成“兜底规则”:前面的正常路由都匹配失败后,最后用一条能匹配任意路径的规则接住。

怎么做:准备一个提示组件,再在路由规则数组的最后加 { path: '*', component: NotFind }。

易错点:不要把 404 规则放在路由数组前面。* 能匹配任意路径,放前面会把正常页面也抢走。

404 规则为什么放在最后 ​

为什么需要:路由匹配通常按规则顺序进行,path: '*' 表示任意路径。它如果排在前面,正常地址还没轮到后面的规则,就先被它命中了。

怎么想:把 404 当成“前面都不匹配才轮到我”的最后一道门。笔记里明确说:404 的路由虽然配置在任何一个位置都可以,但一般都配置在其他路由规则的最后面。

怎么做:在 routes 数组里,把普通路由、嵌套路由等写完后,最后追加通配规则。

易错点:只在脑子里觉得“最后”,实际代码却插在中间;或者后续新增路由时忘了把 404 继续留在末尾。

通配符 path: "*" 的写法 ​

为什么需要:需要一个规则能接住所有未匹配路径,而不是只接住某一个固定地址。

怎么想:* 就是任意路径。前面不匹配就命中最后这个。它不负责业务页面,只负责展示“404 Not Found”提示。

怎么做:先引入提示组件,再写规则:

js
import NotFind from '@/views/NotFind'

const router = new VueRouter({
  routes: [
    ...
    { path: '*', component: NotFind } //最后一个
  ]
})

易错点:组件路径或组件名写错,会导致规则存在但页面报错;path 写成其他字符串就不是通配;引入后忘记在 routes 中使用。

完整代码示例 ​

为什么需要:把提示组件和路由配置分开,结构清晰,也方便以后替换成更完整的 404 页面。

怎么想:NotFound.vue 只负责展示提示;router/index.js 负责引入组件并配置兜底规则。

怎么做:

NotFound.vue:

vue
<template>
  <div>
    <h1>404 Not Found</h1>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

router/index.js:

js
...
import NotFound from '@/views/NotFound'
...

// 创建了一个路由对象
const router = new VueRouter({
  routes: [
     ...
    { path: '*', component: NotFound }
  ]
})

export default router

易错点:示例里 NotFind 和 NotFound 是不同写法,实际项目要统一;import 路径要确认 @/views/NotFound 真实存在;404 规则仍要放在最后。

调试与易错点 ​

为什么需要:404 是否生效,不能只看代码,要看地址变化后路由出口渲染了什么。

怎么想:按转写稿里的匹配思路调试:先改地址,再看有没有规则和它一致;正常地址应命中前面的规则,乱写地址应命中最后的 *。

怎么做:启动项目后输入一个不存在的路径,例如在现有地址后随便加一段不存在的路径,观察是否显示“404 Not Found”;再回到正常页面,确认正常路由没有被 404 抢走。

易错点:如果乱写地址仍空白,优先检查 404 规则是否真的在最后、组件是否引入成功;如果正常页面也变成 404,说明 * 放太前或正常规则被改坏。转写稿没有展开 404 的具体调试,只提到“404 处理特别简单”,因此这里按路由匹配逻辑检查即可,不额外编造步骤。

问题:地址里的 # 从哪里来 ​

为什么需要:默认的 hash 路由地址会变成 http://localhost:8080/#/home,中间的 # 看起来不像普通网页路径。笔记里提出这个问题:能不能切成真正的路径形式?答案是可以用 history 模式,变成 http://localhost:8080/home。

怎么想:先分清两种地址。hash 路由是默认模式,靠 # 后面的部分变化来切换组件;history 路由没有 #,地址更像普通路径。转写稿里也强调:哈希模式“玩的就是哈希地址”,历史模式就是“没有这个井号”,8080 后边直接跟 /discover/recommend 这类路径。

怎么做:先不要急着改代码,先观察当前地址。默认访问时地址里带 #,例如 #/home;目标是把 # 去掉。真正的配置点在创建路由对象时指定 mode,不是去改每个 router-link 的 to。

易错点:不要以为去掉 # 只是“显示好看”。history 模式上线后需要服务器端支持;笔记明确写了“以后上线需要服务器端支持,开发环境 webpack 给规避掉了 history 模式的问题”。本地开发能正常刷新,不代表上线后刷新 /home 也一定正常。

两种模式:hash 与 history 的区别 ​

为什么需要:知道区别,才能判断什么时候用默认 hash、什么时候改 history。hash 路由地址如 http://localhost:8080/#/home;history 路由如 http://localhost:8080/home。转写稿里说,history 模式地址看起来更符合语义,公司内部项目一般用 hash 也 OK;工作中到底用哪种,往往不是初学者自己决定,项目经理可能已经配好。

怎么想:hash 模式利用 # 后面的片段,浏览器不会把 # 后内容当作服务器路径去请求;history 模式使用浏览器 history 能力改变地址,地址栏没有 #,但刷新或直接访问真实路径时,需要服务器把请求回退到入口页面。开发环境下 webpack 已经规避了这个问题,所以本地调试通常感觉不到。

怎么做:如果只是内部项目、不想折腾服务端,保持默认 hash 即可;如果要地址自然、接近真实路径,就改为 history。改完后所有路由地址会自动变成无 # 形式,转写稿里验证了“我的音乐、关注、歌手”等地址都没有 #,其他链接代码不用改。

易错点:不要以为 history 模式会改变路由规则。routes 还是原来的规则;也不要以为改完 mode 后旧带 # 的地址还能照常使用。转写稿提醒:换模式后要从 localhost:8080 重新看起,因为旧地址一瞬间会乱;如果还停在旧 hash 地址上,容易误判“怎么没生效”。

配置语法:在 VueRouter 里指定 mode ​

为什么需要:模式是路由对象的全局配置,不是某个组件的配置。笔记给出的语法就是在 new VueRouter 时加 mode。

怎么想:默认是 hash,所以不写 mode 也能跑;要 history 就显式写 mode: 'history'。注意笔记代码里写的是 mode:'histroy',这是拼写错误,正确单词是 history,不能照抄错词。

怎么做:保留笔记代码形态如下:

js
const router = new VueRouter({
    mode:'histroy', //默认是hash
    routes:[]
})

实际应改为:

js
const router = new VueRouter({
    mode: 'history', // 默认是 hash
    routes: []
})

改完后从根地址重新进入页面,观察地址栏是否还有 #。转写稿的调试过程是:改 mode 后从 8080 重新看,地址里没有井号了,其他页面地址也自动无 #,路由切换照常。

易错点:第一,histroy 拼错会导致配置不生效或行为不符合预期;第二,只改 mode 不要顺手改 router-link 的 to;第三,改完不要继续点旧地址判断,必须从根地址重新进入;第四,上线前确认服务端支持 history 回退,否则刷新真实路径可能 404。

上线与选择:什么时候用哪种模式 ​

为什么需要:本地开发和生产环境不一样。笔记专门提示 history 模式“以后上线需要服务器端支持”,这是选型时必须知道的前提。

怎么想:如果项目部署环境不方便配置服务端回退,hash 模式最稳;如果公司项目已经配好 history 回退,就用 history 获得更自然的 URL。转写稿里也说,历史模式地址更符合语义,但公司内部用 hash 也 OK,实际用哪种要看项目统一配置。

怎么做:开发阶段按笔记改 mode: 'history' 验证效果;上线前确认服务器对非静态路径返回入口 HTML,开发环境则由 webpack 规避 history 模式的问题。不要额外改路由规则和链接写法。

易错点:把“本地能刷新”误当成“上线也能刷新”;把 mode 当成路由规则的一部分;在旧带 # 地址上反复测试;以及照抄 histroy 拼写。记住本节核心:默认 hash 带 #,history 去 #,配置在 new VueRouter({ mode: 'history' }),上线需服务端支持。

问题:点击按钮跳转如何实现? ​

点击按钮跳转的示意图

为什么需要:前面用 router-link 做跳转,本质是“超链接”触发地址变化。转写稿里反复强调:超链接点完后,首先变的是地址栏;地址变了,再拿地址去和路由规则匹配,匹配到才展示对应组件。但实际页面里经常是按钮、提交后、条件判断后再跳转,这些不能只靠超链接,所以需要“用 JS 代码来进行跳转”,也就是编程式导航。

怎么想:不要把它想复杂,编程式导航仍然是在改地址栏,只是触发方式从“点超链接”变成“执行 JS 跳转语句”。地址变化后,后续流程仍是:匹配路由规则 → 展示组件。调试时也按这个顺序看:先看地址栏有没有变,再看规则有没有匹配。

怎么做:在事件处理里调用 this.$router.push(...)。本节只讲两种写法:path 路径跳转和 name 命名路由跳转。

易错点:地址变了但页面空白,优先查路由规则是否匹配;不要只盯着跳转语句。转写稿调试动态路由时就是先看地址,再看“有没有相同规则”,没有规则就匹配不到组件。

path 路径跳转 ​

为什么需要:path 写法直接写路由路径,简易方便,适合路径短、规则清楚的场景。

怎么想:把 push 的参数理解成“我要去哪个路径”。简单写法直接传字符串;完整写法传对象,用 path 指定路径。

怎么做:保留笔记中的代码:

js
//简单写法
this.$router.push('路由路径')

//完整写法
this.$router.push({
  path: '路由路径'
})

笔记里的“代码演示 path 跳转方式”没有展开具体页面代码,这里不补写笔记外代码;实际就是把点击事件中的跳转写成上面的 push 形式。

易错点:path 要和路由规则里的路径对得上。转写稿里多次出现“地址变了,但没有相同规则,所以下面一定空白”的调试过程,这个排查顺序同样适用于 path 跳转:先核对地址栏,再核对规则。

name 命名路由跳转 ​

为什么需要:当 path 很长时,每次写完整路径容易写错、也不好维护。给路由规则起一个 name,跳转时只写路由名,更适合路径长的场景。

怎么想:name 跳转的前提是“路由规则必须配置 name 配置项”。也就是说,先给规则起名字,再通过名字跳。它跳转的依据不是路径字符串,而是规则里的 name。

怎么做:笔记要求两步。第一步,路由规则配置 name:

js
{ name: '路由名', path: '/path/xxx', component: XXX },

第二步,通过 name 跳转:

js
this.$router.push({
  name: '路由名'
})

笔记里的“代码演示通过 name 命名路由跳转”同样没有展开页面代码,不补写;核心就是上面两段。

易错点:路由规则没写 name,或 name 写错,都会导致按名字跳不过去。排查时仍按转写稿的顺序:看地址栏是否变化,再看规则是否能匹配到对应组件。

总结:两种方式怎么选 ​

为什么需要总结:编程式导航不是只有一种写法,选择时要看路径长短和维护成本。

怎么想:path 路径跳转简易方便;name 命名路由跳转适合 path 路径长的场景。name 方式必须先给路由规则配置 name。

怎么做:本节问题的答案是:编程式导航有两种跳转方式——path 路径跳转和 name 命名路由跳转。短路径用 path 更直接;长路径用 name 更清晰。

易错点:不要把“编程式导航”和“声明式导航”混在一起;router-link 是点超链接改地址,this.$router.push 是用 JS 改地址。无论哪种,地址变化后都要经过路由规则匹配,匹配不到就展示不了目标组件。

问题:搜索按钮如何把输入带到下一页 ​

搜索按钮跳转传参示意图

为什么需要:点击搜索按钮后,目标页面往往要根据关键词展示结果;如果只跳页面不传参,下一页就不知道用户搜了什么。怎么想:先把“输入内容”变成地址栏上的参数,再在目标组件里通过路由对象取出来。怎么做:用编程式导航 this.$router.push(...) 让地址栏变成带参数的格式,目标组件用 $route.query 或 $route.params 接收。易错点:不要只跳转不挂参数;参数名前后要一致;调试时先看地址栏有没有变成预期格式。

先分清两种参数格式与两种跳转方式 ​

为什么需要:路由传参常见两种格式,先分清才不会把 query 和 params 混用。怎么想:查询参数形如 /路径?参数名=参数值&参数2=参数值2,用问号和 & 连接;动态路由传参形如 /路径/参数值,参数值直接成为路径的一段。转写稿中用网易云音乐歌手页说明:同一个页面靠 ID 区分不同歌手,地址参数一变,页面内容就换。怎么做:两种跳转方式——① path 路径跳转传参;② name 命名路由跳转传参——都支持这两种传参。本节只讲 path 路径跳转传参。易错点:一个项目里最好统一风格;不要把查询参数写成路径段,也不要把动态路由参数硬塞进 query。

path 路径跳转传参(query 传参) ​

为什么需要:搜索关键词、筛选条件等参数不适合塞进路径结构,放在问号后面最直观。怎么想:先让地址栏变成 /路径?参数名1=参数值1&参数2=参数值2,再在目标组件用 $route.query.参数名 取。怎么做:

js
//简单写法
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
//完整写法
this.$router.push({
  path: '/路径',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

接收参数的方式依然是:$route.query.参数名。易错点:query 里的参数名要和接收处一致;多个参数用 &;path 要写目标页面路径。调试时先点击按钮看地址栏是否出现 ?参数名=参数值,再在目标组件打印 this.$route.query.参数名。转写稿中小结也强调:查询参数地址栏是 ?id=...,路由规则无需改变,取值用 $route.query.id。

path 路径跳转传参(动态路由传参) ​

为什么需要:详情页这类参数本身就是页面身份的一部分,如 /detail/3,用动态路由更清楚。怎么想:把参数值直接拼进 path,地址栏形成 /路径/参数值;目标组件用 $route.params.参数值 取。怎么做:

//简单写法
this.$router.push('/路径/参数值')
//完整写法
this.$router.push({
  path: '/路径/参数值'
})

接收参数的方式依然是:$route.params.参数值。易错点:动态路由要求路由规则能匹配地址,转写稿中点击后若页面空白,就是地址变了但没有对应规则,需要把规则写成带动态段的格式,如 /路径/:参数名;参数值变化时组件可能复用,调试可刷新或监听参数变化。先看地址栏是否变成 /路径/参数值,再看 $route.params 是否有值。

注意:path 不能配合 params 使用 ​

为什么需要:很多人会顺手写成 { path: '/路径', params: {...} },以为能传动态参数,结果取不到。怎么想:params 通常要配合命名路由 name 使用;如果坚持用 path,就把参数值直接拼进路径,或用 query。怎么做:动态路由传参时写 this.$router.push('/路径/参数值');需要对象写法时也只写 path: '/路径/参数值'。易错点:不要写 path 又写 params;不要混淆 $route.query 和 $route.params。转写稿中小结表也提醒:动态路由地址栏是 /.../值,路由规则要改成带动态段,取值用 $route.params.id;而查询参数用 $route.query.id。本节核心就是:先把地址栏变成目标格式,再按对应方式取值。

一、命名路由传参要解决什么问题 ​

编程式导航指的是在代码里调用 this.$router.push(...) 完成跳转,而不是只写 <router-link>。命名路由则是给路由规则起一个 name,跳转时用 name 找路由,不必手写完整路径。

为什么需要它?转写稿里举了歌手页、面经详情页的例子:同一个页面要根据不同参数展示不同内容,不可能为每个歌手、每道题都做一个页面。参数一改,页面内容就换。路由传参常见有两种格式:?id=3&name=xxx 这种查询参数,以及 /路径/参数值 这种动态路由参数。命名路由跳转同样支持这两种传参。

怎么想?先判断参数是“查询条件”还是“路径的一部分”。查询参数用 query,动态路由参数用 params,并且动态路由必须先在路由规则里配置动态段。怎么做?看下面两类写法。易错点:name 必须和路由规则里的 name 一致;query 与 params 的获取位置不同,不能混用。

二、name + query 传参 ​

为什么需要:query 适合可选参数、筛选条件、详情 ID 等,地址形如 /路径?参数名1=参数值1&参数名2=参数值2。它的好处是路由规则通常不需要为参数额外改造,刷新后参数仍在地址栏。怎么想:用 name 指定目标路由,用 query 对象挂参数。怎么做:

js
this.$router.push({
  name: '路由名字',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

跳转后,在目标组件中用 this.$route.query.参数名 获取。转写稿的调试过程是:先点击/触发跳转,看地址栏有没有变成 ?ID=...;地址正确后,再在组件里打印 this.$route.query.ID 验证。易错点:不要写成 this.$route.params.ID;参数名大小写要一致;name 写错会导致找不到路由;query 的值从地址栏来,通常是字符串,参与计算时要注意类型。

三、name + 动态路由 params 传参 ​

为什么需要:动态路由把参数放进路径里,例如 /路径/参数值,适合“资源 ID”这类参数。转写稿里强调:动态路由传参需要配动态路由规则,否则地址变了也可能匹配不到组件。怎么想:路由规则里用 :参数名 占位,跳转时用 params 传同名参数。怎么做:

js
this.$router.push({
  name: '路由名字',
  params: {
    参数名: '参数值',
  }
})

对应路由规则要能匹配动态段,例如路径中配置 :参数名。目标组件中用 this.$route.params.参数名 获取。转写稿的排错过程很典型:点击后先看地址栏是否变成斜线参数;如果地址变了但下面空白,通常是路由规则没有匹配上,需要检查动态段是否配置;若参数可选,可在规则中按转写稿思路加 ? 表示可有可无。易错点:没配动态路由就收不到/匹配不到;获取时用 params 而不是 query;params 里的参数名要和规则中的占位名对应;不要把动态路由参数和查询参数混在同一个获取方式里。

四、path 与 name 的四种写法总结 ​

笔记总结了两大类:path 路径跳转和 name 命名路由跳转,每类又分 query 与动态路由。

1)path 路径跳转 + query 传参:

js
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
this.$router.push({
  path: '/路径',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

2)path 路径跳转 + 动态路由传参:

js
this.$router.push('/路径/参数值')
this.$router.push({
  path: '/路径/参数值'
})

3)name 命名路由跳转 + query 传参:

js
this.$router.push({
  name: '路由名字',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

4)name 命名路由跳转 + 动态路由传参,需要配动态路由:

js
this.$router.push({
  name: '路由名字',
  params: {
    参数名: '参数值',
  }
})

易错点:用 name + params 时,重点检查路由规则是否配置了动态段;用 query 时重点检查获取处是不是 $route.query。转写稿还提醒,实际项目里两种参数格式都可能用,但一个项目通常会保持统一风格。

五、调试顺序与易错点归纳 ​

调试时按转写稿的顺序走:第一步看地址栏有没有按预期变化,是 ?参数=值 还是 /路径/参数值;第二步看地址能否和路由规则匹配,匹配不上页面就可能空白;第三步在目标组件中打印 this.$route.query 或 this.$route.params,确认参数是否取到。编程式导航只是把“点击超链接改地址”换成代码调用 push,核心仍是地址变化后由路由规则匹配组件。常见错误是:name 与规则不一致、动态路由没配占位、query/params 获取混用、参数名拼写不一致。按“先地址、再规则、后取值”的顺序排查,问题通常很快能定位。

面经案例效果演示 ​

为什么需要:先看成品效果,才能把“要做什么”拆成“怎么做”。
怎么想:观察页面跳转和局部变化,而不是一上来写代码。
怎么做:演示中从面经列表点击某一条,进入面经详情;底部导航可以来回切换,切换时只有上方主题内容在动态渲染。
易错点:不要把“每个面经详情”理解成单独页面,否则页面数量会失控。

面经案例列表页与详情页演示

功能分析:列表页与详情页 ​

为什么需要:效果只是表象,功能分析决定路由和组件划分。
怎么想:主要功能页面有两个:列表页和详情页;列表页点击可跳转到详情页;底部导航切换时,只有主题内容区变化。
怎么做:把列表页作为首页主体,把详情页作为一级路由页面;底部导航对应首页内可切换的二级页面。
易错点:把底部导航的每个切换项都做成一级页面,导致嵌套关系混乱。

实现思路:配置路由与功能实现 ​

为什么需要:路由负责“地址与组件”的对应关系,功能实现负责数据与交互。
怎么想:配置路由时,首页和面经详情页是两个一级路由;首页内嵌套4个可切换页面,形成嵌套二级路由。功能上完成首页请求渲染、跳转传参到详情页、详情页动态渲染,以及组件缓存做性能优化。
怎么做:先写路由规则,再设置路由出口;一级路由组件展示在App的出口中,二级路由组件展示在首页组件的出口中。
易错点:一级路由出口和二级路由出口放错位置;嵌套路由忘记写children。

面经案例路由与功能实现思路

跳转传参:查询参数 ​

为什么需要:详情页必须知道“展示哪一条面经”,否则只能写死内容。
怎么想:地址栏用问号携带参数,例如?id=3,详情页再取出来发请求。
怎么做:把普通a标签改成router-link,to前加冒号,写成类似/discover/arlist?id=${item.id};组件中用this.$route.query.id获取。
易错点:to前漏写冒号导致变量不解析;跳转地址写成别的页面;组件不刷新时created只执行一次,调试时可刷新页面确认参数。

动态路由传参 ​

为什么需要:面经详情这类页面更适合把ID直接放在路径里,地址更直观。
怎么想:用斜线拼参数,例如/discover/toplist/3/汪苏泷;路由规则也要改成带参数的形式。
怎么做:规则写成path: '/discover/toplist/:name?',问号表示参数可选;组件中用this.$route.params.name获取。
易错点:只改跳转地址、不改路由规则,页面会空白;可选参数要加问号,否则不传参时匹配不到。

重定向与历史模式 ​

为什么需要:打开根路径不能空白,地址栏也希望能去掉井号。
怎么想:重定向就是访问/时自动改到推荐页;历史模式通过mode: 'history'去掉#。
怎么做:加规则redirect: '/discover/recommend';创建路由对象时设置mode: 'history'。
易错点:根路径规则不要重复写;换模式后从8080重新打开检查,其他链接一般不用改。

组件缓存与性能优化 ​

为什么需要:列表页反复进入时,如果每次都重新请求和渲染,体验和性能都会变差。
怎么想:把需要保留状态的列表组件缓存起来,返回时不重复初始化。
怎么做:按项目需要缓存列表页,具体用组件缓存机制包裹对应路由出口;详情页通常不缓存。
易错点:不要所有页面都缓存,否则详情页可能显示上一条数据;缓存范围要按业务确定。

课件图示 ​

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

课件第27页:问题: 路由的路径看起来不自然, 有#,能否切成真正路径形式? l hash路由

课件第29页:两种语法: ① path 路径跳转 ② name 命名路由跳转 编程式导航 -

课件第31页:两种语法: ① path 路径跳转 ② name 命名路由跳转 (适合 path

课件第35页:两种传参方式:查询参数 + 动态路由传参 两种跳转方式,对于两种传参方式都支持:

课件第36页:两种传参方式:查询参数 + 动态路由传参 两种跳转方式,对于两种传参方式都支持:

课件第38页:两种传参方式:查询参数 + 动态路由传参 两种跳转方式,对于两种传参方式都支持:

课件第26页:Vue路由 - 404 作用:当路径找不到匹配时,给个提示页面 位置:配在路由最

代码示例 ​

示例一:src/router/index.js —— 路由规则总配置(重定向 + 动态路由 + 命名路由 + 404 + 模式) ​

js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Discover from '@/views/Discover'
import TopList from '@/views/TopList'
import ArList from '@/views/ArList'
import NotFound from '@/views/NotFound'

Vue.use(VueRouter)

const router = new VueRouter({
  // mode: 'hash' 是默认值,地址带 #
  // 改成 history 后地址没有 #,上线需要服务端支持
  mode: 'history',

  routes: [
    // 1. 重定向:打开 http://localhost:8080/ 时自动跳到推荐/排行榜
    { path: '/', redirect: '/discover/toplist' },

    // 2. 一级路由 Discover,内部嵌套二级路由
    {
      path: '/discover',
      component: Discover,
      children: [
        // 动态路由传参::name 就是参数占位,? 表示该参数可有可无
        { name: 'topList', path: 'toplist/:name?', component: TopList },
        // 命名路由:给 arList 起个名字,方便 name 方式跳转
        { name: 'arList', path: 'arlist', component: ArList }
      ]
    },

    // 3. 404 规则:path 为 * 表示任意路径,必须放在所有规则的最后面
    { path: '*', component: NotFound }
  ]
})

export default router

关键行与易错点

  • path: '*' 是“兜底规则”,一定写在 routes 数组的最后,否则前面的规则会被它抢走匹配。
  • mode 的值是 'history'(注意拼写,写成 histroy 不生效,仍走默认 hash)。改完模式后要从 http://localhost:8080/ 重新进入,否则旧地址会乱。
  • 动态路由规则里 toplist/:name? 的 ? 表示参数可选:/discover/toplist 和 /discover/toplist/飙升榜 都能匹配到这条规则。如果写成 toplist/:name 没有问号,直接点“排行榜”就会因为匹配不到规则而显示空白。
  • redirect 是“改地址”而不是渲染组件;如果已经有 path: '/' 的规则,直接在上面加 redirect,不要重复再写一条 path: '/'。
  • 二级路由要能显示,Discover.vue 模板里必须留 <router-view></router-view> 出口。

示例二:src/views/ArList.vue —— path 路径跳转 + query 查询参数传参 ​

vue
<template>
  <div>
    <!-- 超链接挂参数:加冒号后 to 里就是表达式,要用模板字符串 -->
    <div v-for="item in list" :key="item.id">
      <img :src="item.smallImg" alt="" />
      <p>
        <router-link :to="`/discover/arlist?id=${item.id}`">
          {{ item.name }}
        </router-link>
      </p>
    </div>

    <!-- 下方大图:根据地址栏 id 找到对应数据 -->
    <div>
      <img :src="getURL.bigImg" alt="" />
    </div>

    <button @click="goDetail(3)">编程式导航跳转(path + query)</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 1, name: '张惠妹', smallImg: 'small1.jpg', bigImg: 'big1.jpg' },
        { id: 2, name: '汪苏泷', smallImg: 'small2.jpg', bigImg: 'big2.jpg' },
        { id: 3, name: '邓紫棋', smallImg: 'small3.jpg', bigImg: 'big3.jpg' }
      ]
    }
  },
  computed: {
    // 计算属性必须有返回值
    getURL() {
      // 地址栏有 id 才去查找,否则首次进入会报错
      if (this.$route.query.id) {
        return this.list.find(item => item.id === +this.$route.query.id)
      }
      return {}
    }
  },
  methods: {
    // 编程式导航:path 路径跳转 + query 传参
    goDetail(id) {
      this.$router.push({
        path: '/discover/arlist',
        query: { id }
      })
    }
  }
}
</script>

关键行与易错点

  • to 前面必须加冒号 :to,加了冒号就是 JS 表达式,字符串片段要用反引号包起来,变量用 ${} 拼接;不加冒号只能写死一个字符串路径。
  • router-link 的 href 不要再手写,用 to 即可,它真正干的事就是改地址栏。
  • 跳转地址必须是当前页面(/discover/arlist),写成别的路径(比如 /discover/toplist)就会跳到别的组件去。
  • 取参用 this.$route.query.id($route 没有 r,$router 才是跳转对象,别混)。
  • query 取到的永远是字符串,和 item.id(数字)比较要 +this.$route.query.id 转成数字,否则 find 返回 undefined,大图渲染报错。
  • 计算属性里一定要先判断 this.$route.query.id 是否存在,首次进入没有参数时返回 {},否则 undefined.bigImg 直接报错。

示例三:src/views/TopList.vue —— 动态路由传参 + name 命名路由传参 ​

vue
<template>
  <div>
    <ul>
      <!-- 动态路由传参:斜线直接拼参数值 -->
      <li v-for="item in list" :key="item.id">
        <router-link :to="`/discover/toplist/${item.name}`">
          {{ item.name }}
        </router-link>
      </li>
    </ul>

    <!-- 显示当前查看的是哪个榜 -->
    <div>你查看的是:{{ $route.params.name }}</div>

    <button @click="goTop('新歌榜')">name 命名路由跳转 + params 传参</button>
    <button @click="goArList(2)">name 命名路由跳转 + query 传参</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 1, name: '飙升榜' },
        { id: 2, name: '新歌榜' },
        { id: 3, name: '原创榜' },
        { id: 4, name: '热歌榜' }
      ]
    }
  },
  methods: {
    // 命名路由 + 动态路由传参(要求规则里有 :name 占位)
    goTop(name) {
      this.$router.push({
        name: 'topList',
        params: { name }
      })
    },
    // 命名路由 + query 传参
    goArList(id) {
      this.$router.push({
        name: 'arList',
        query: { id }
      })
    }
  }
}
</script>

关键行与易错点

  • 动态路由传参只需要改地址栏形式:/discover/toplist/新歌榜,不用写成 ?name=;取参用 $route.params.name。
  • path 不能和 params 搭配使用:this.$router.push({ path: '/xxx', params: {...} }) 里的 params 会被丢掉;要用 params 就必须用 name 方式跳转。
  • 用 name + params 时,路由规则里必须真的配了 :name 占位({ name: 'topList', path: 'toplist/:name?' }),否则参数取不到。
  • name 命名路由跳转要求路由规则里配置了 name 配置项,名字写错会报“找不到路由”。
  • 两种传参小结:查询参数 ?id=1 → 规则无需改动,取 $route.query.id;动态路由 /xxx/1 → 规则要写成 :id,取 $route.params.id。同一个项目里通常统一用一种。

常见错误 ​

  1. 现象:随便输入什么地址都显示 404 页,正常页面打不开。 原因:{ path: '*' } 写在了 routes 数组前面,任意路径先被它截胡。 修法:把它挪到数组最后一条;确认前面没有别的兜底规则。

  2. 现象:改了 mode 地址栏还是带 #,或者配置了却毫无反应。 原因:值拼错(如 'histroy')或大小写不对,VueRouter 不报错只当无效值忽略。 修法:写成 mode: 'history';改完从 localhost:8080 根地址重新进入再看,不要在旧 hash 地址上刷新观察。

  3. 现象:push 后 $route.params.id 永远是 undefined。 原因:用了 path 方式跳转却同时传 params,path 与 params 互斥,params 被静默丢弃。 修法:要么改成 name + params,要么把值拼进路径 /detail/3 并配好 /:id 规则。

  4. 现象:点击后页面一片空白,控制台没有任何报错。 原因:地址已经变成 /toplist/1,但路由规则还停在 /toplist,没有规则能匹配上。 修法:规则改成 /toplist/:id;参数可有可无时写 /:id?;改完先在地址栏核对拼出来的路径与规则是否一致。

  5. 现象:$router.query.id 取不到值,或调用 push 报 “not a function”。 原因:$router(路由实例,负责跳转)与 $route(当前路由信息,负责取参)写混。 修法:跳转用 this.$router.push,取参用 this.$route.query.id / this.$route.params.id。

  6. 现象:详情页报 Cannot read property 'xxx' of undefined,尤其是首次进入或手改地址后。 原因:直接进页面时地址上没有 id,find 返回 undefined,还继续取它的属性。 修法:计算属性里先判断 $route.query.id 是否存在,不存在返回 {} 兜底,模板上再做空值保护。

  7. 现象:配了重定向却报重复路由,或首页仍空白。 原因:routes 里已经有 path: '/' 的规则,又新增了一条同样的。 修法:先搜有没有 /,有就原地加 redirect,没有才新增;redirect 目标要写完整可匹配的路径。

练习 ​

  1. 给现有项目补一个 404 页面:分别访问 /、/discover/recommend、/xxx/yyy 验证前两个正常、第三个落到 404;再把 * 规则临时挪到最前面复现问题并说明原因。

  2. 面经模块:列表页点击任意一条跳到详情页,要求用「命名路由 + 动态路由传参」实现,详情页拿到 id 后渲染对应数据;再让 id 变成可选(不传 id 时页面给出提示而不报错)。

  3. 把歌手页现有的 query 传参改造成「命名路由 + params 传参」,记录改造前后地址栏的变化,并说明这次改造需不需要动路由规则、为什么。

  4. 开启 history 模式,从根地址进入后依次访问三个不同层级的子路由并刷新,确认都不 404;再写出上线部署时服务端需要补的那一条配置思路。

  5. 首页默认展示「推荐」:给 / 加重定向,并说明如果 routes 里已经存在 path: '/' 的规则应当如何修改而不是新增。

原视频对照 ​

#视频时长
1路由传参的两种方式06:52
2查询参数传参12:03
3上午知识回顾09:38
4动态路由传参12:03
5路由传参小结04:25
6重定向05:05
7历史模式03:03

小结 ​

  • 404 兜底规则必须放最后,它的语义是“前面都没匹配上才轮到我”。
  • 传参方式与跳转方式要配套:path 只能配 query 或拼路径,params 必须配 name。
  • 动态路由传参不是只改跳转代码,路由规则同步要加 /:参数名,这是最容易漏的一步,漏了表现为空白页而非报错。
  • 参数可选时用 ? 修饰,可以让“带参”和“不带参”两种地址都命中同一条规则。
  • 跳转传参只解决“把值带过去”,拿到值之后怎么查数据、怎么渲染属于业务逻辑,不要和路由混为一谈。
  • $router 管跳转、$route 管取参,混用是高频低级错误。
  • history 模式只是去掉 #,开发环境 webpack 已规避刷新问题,上线必须服务端配合,具体用哪种模式通常由项目决定而非个人偏好。
  • 重定向和 404 都是“补规则”,动手前先确认已有规则里是否已经存在同一个 path。