主题
路由传递参数(重点)与二级路由(Lesson10 3/3)
本页对应 Lesson10,共 5 段视频,约 1 小时 37 分。
学习目标
- 能在路由配置中使用
children定义二级路由,并在父级组件中通过<router-view>正确渲染子路由内容。 - 能实现二级路由的跳转与传参,包括完整路径、相对路径、命名路由、对象传参,并能通过
$route.params读取参数。 - 能按“无导航页面作为一级路由、需导航页面作为地址为
/的二级路由”的结构搭建卖座基本路由,并配置默认重定向。 - 能实现“正在热映”和“即将上映”的切换、列表渲染及点击电影跳转详情页,并根据路由参数请求对应详情数据。
- 能通过路由
meta配置控制导航链接的显示与隐藏,并验证详情页等无导航页面不显示导航。 - 能使用过滤器或映射方法处理接口数据中的演员列表和秒级时间戳,完成上映日期等内容的格式化渲染。
知识点
二级路由:先把“谁里面再出导航”想清楚
为什么需要:一级路由只能在 APP 里切换大页面;当“新闻列表”里还要有“体育/娱乐/财经”,或卖座里“正在热映/即将上映”还要共用一块导航时,就需要子路由。转写稿里反复强调:二级路由也叫子路由,概念和之前学的路由一致。
怎么想:先找“父级路由组件”,再在它里面放 router-view。一级路由的 router-view 在 APP 里;二级路由的 router-view 要放到父级组件里,否则子组件没有渲染位置。
怎么做:在父路由配置里加 children,子路由 path 一般不写开头的 /,它会自动拼到父路径后面。示意代码按转写稿整理:
js
// router/index.js
const routes = [
{
path: '/news',
component: NewsList,
children: [
{ path: 'sports', name: 'sports', component: Sports },
{ path: 'ent', name: 'ent', component: Ent },
{ path: 'finance', name: 'finance', component: Finance }
]
}
]html
<!-- NewsList.vue -->
<router-link to="/news/sports">体育新闻</router-link>
<router-link to="/news/ent">娱乐新闻</router-link>
<router-link to="/news/finance">财经新闻</router-link>
<router-view></router-view>易错点:子路由 path 写成 /sports 会按根路径理解;父组件里忘写 router-view,地址变了但内容不出来;router-link 地址要写完整或正确使用相对地址,转写稿明确说相对地址“可行但不建议”,容易拼错层级。
默认选中:重定向或同地址加 exact
为什么需要:点“新闻列表”进入父路由时,下面没有默认子项,用户看不到内容,也不知道当前选中谁。
怎么想:转写稿给了两种方案。方案一:让默认子路由地址与父路由相同,并配合 exact 避免所有子链接都高亮。方案二:父路由重定向到默认子路由。
怎么做:
js
// 方案二:重定向
{ path: '/news', redirect: '/news/sports' }html
<!-- 方案一思路:默认项地址与父级相同,并加 exact -->
<router-link to="/news" exact>体育新闻</router-link>易错点:不加 exact 时,/news 会模糊匹配到多个子项,导致全都高亮;重定向后地址会变成默认子路由地址,这是正常现象。
详情页传参:动态路由 :filmId 与 $route.params
为什么需要:卖座列表点某部电影,详情页要知道“点的是哪一部”,所以要把 ID 通过路由传过去。
怎么想:转写稿里详情地址形如 /film/6445,路由配置用动态段接住,组件里用 $route.params 取。点击时用 router-link 的 to 拼接 ID。
怎么做:
js
{ path: '/film/:filmId', component: Detail }html
<router-link :to="'/film/' + item.filmId">详情</router-link>js
// Detail.vue
this.filmId = this.$route.params.filmId易错点:转写稿调试时出现过“详情空白”,先查地址是 /film 还是 /film/xxx,再查路由参数名和 $route.params 里的名字是否一致;详情接口的请求头也可能和列表接口不同,不能直接照搬。列表渲染还出现过图片字段用 poster、主演是数组需 map 或过滤器拼接的问题,说明“能拿到数据”不等于“字段路径写对”。
隐藏导航:meta 判断与二级路由设计法
为什么需要:正在热映、即将上映有导航;点详情后不要导航。转写稿先用 v-if 判断路由名,再改成 meta 标记,最后提出更高级的做法:把“没有导航的详情/登录”作为一级路由,把“需要导航的正在热映/即将上映”作为某个一级路由下的二级路由。
怎么想:APP 只挂载一次,切换路由时执行 updated,说明视图会更新,可以在 APP 里根据当前路由决定导航显示。用 $route.meta 比写死数组更稳。
怎么做:
js
{ path: '/film/:filmId', component: Detail, meta: { isHide: true } }html
<nav v-if="!$route.meta.isHide">...</nav>二级路由设计法示意:
js
{
path: '/index',
component: Index,
children: [
{ path: 'noPlay', component: NoPlay },
{ path: 'comingSoon', component: ComingSoon }
]
}易错点:meta.isHide 为 true 表示隐藏,判断时要取反;不要每加一个页面就改多处 v-if,转写稿建议把是否隐藏收敛到路由配置里。children 下的子路由要在父组件 Index 的 router-view 中渲染,不要误放到 APP 的 router-view 里。
二级路由是什么:先理解“子路由”的匹配顺序
为什么需要:一级路由只能解决“首页、新闻、商品、我的”这种大页面切换;但新闻列表下面还要分体育、娱乐、财经,卖座下面还要分正在热映、即将上映,这时就需要二级路由,也叫子路由。
怎么想:二级路由不是独立存在的,必须先匹配到一级路由,再在一级路由下面匹配子路由。例如 /news/sport,先找到 /news 对应的 NewsList,再在它下面找 sport。因此一级组件里必须留一个渲染位置。
怎么做:在 router/index.js 中给一级路由加 children:
js
{
path: '/news',
component: NewsList,
children: [
{ path: 'sport', component: Sport },
{ path: 'ent', component: Ent },
{ path: 'finance', component: Finance }
]
}然后在 NewsList.vue 里写导航和二级出口:
vue
<template>
<div>
<router-link to="/news/sport">体育新闻</router-link>
<router-link to="/news/ent">娱乐新闻</router-link>
<router-link to="/news/finance">财经新闻</router-link>
<router-view></router-view>
</div>
</template>易错点:子路由 path 不要随手写成 /sport,斜线开头会被当成根级地址;二级路由的 router-view 要放在一级组件里,不要只放在 App.vue 里,否则二级内容没有出口。转写稿里点击新闻后下面不显示,就是因为还没在一级组件里放渲染位置。
默认子路由与重定向:解决“点新闻后下面空白”
为什么需要:访问 /news 时,三个子路由都不匹配,页面会只显示一级导航,下面空白。需要默认进入体育新闻或做重定向。
怎么想:转写稿给了两种方案。方案一是让默认子路由地址与上一级相同,并处理高亮精确匹配;方案二更直观:访问 /news 时重定向到 /news/sport。
怎么做:用 redirect:
js
{
path: '/news',
component: NewsList,
redirect: '/news/sport',
children: [
{ path: 'sport', component: Sport },
{ path: 'ent', component: Ent },
{ path: 'finance', component: Finance }
]
}易错点:重定向要写在父级路由上;如果父级链接一直高亮,转写稿里用 exact 做精确匹配,实际写法要按你使用的 Vue Router 版本确认。默认子路由不要和别的子路由地址冲突。
地址写法:完整路径、相对路径、命名路由
为什么需要:to 写不好,点击后会跳到错误地址。转写稿专门试了完整地址、相对地址和对象写法。
怎么想:完整地址最直观;相对地址相对当前路由,容易多一层或少一层;命名路由最稳,改路径时不用到处改链接。
怎么做:
vue
<router-link to="/news/sport">体育新闻</router-link>
<router-link to="sport">体育新闻</router-link>
<router-link :to="{ name: 'sport' }">体育新闻</router-link>对应路由加 name:
js
{ path: 'sport', name: 'sport', component: Sport }易错点:转写稿里相对地址能试出效果,但不建议;斜线代表根目录,去掉斜线可能变成当前目录下的相对地址。命名路由要保证 name 不重复。
卖座案例:用层级设计隐藏详情页导航
为什么需要:正在热映、即将上映有顶部导航;点进电影详情后不要导航。转写稿先试了 v-if 判断路由名或 meta,最后推荐用路由层级解决。
怎么想:把“没有导航的详情页”作为一级路由;把“需要导航的正在热映、即将上映”作为 / 下的二级路由。这样详情页直接在 App.vue 的 router-view 渲染,不经过带导航的 Index 组件。
怎么做:
js
const routes = [
{ path: '/film/:filmId', component: FilmDetail },
{
path: '/',
component: Index,
redirect: '/now-playing',
children: [
{ path: 'now-playing', component: NowPlaying },
{ path: 'coming-soon', component: ComingSoon }
]
}
]Index.vue 里放导航和二级出口:
vue
<template>
<div>
<router-link to="/now-playing">正在热映</router-link>
<router-link to="/coming-soon">即将上映</router-link>
<router-view></router-view>
</div>
</template>易错点:详情路由不要误放进 children,否则它会继承 Index 的导航;子路由 path 不要再写 /;redirect 要指向真实存在的子路由。转写稿反复调试“点详情还有导航”,本质就是层级放错了。
课堂画面





代码示例
示例一:二级路由(子路由)+ 默认子路由
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>二级路由(子路由)</title>
<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>
<style>
body { font-family: sans-serif; }
.nav a, .sub-nav a { margin-right: 12px; text-decoration: none; color: #333; }
.router-link-active { color: red; }
.sub-nav { padding: 8px 0; border-bottom: 1px dashed #ccc; }
</style>
</head>
<body>
<div id="app">
<!-- 一级路由的导航 -->
<div class="nav">
<router-link to="/home" exact>首页</router-link>
<router-link to="/news">新闻列表</router-link>
<router-link to="/goods">商品列表</router-link>
</div>
<hr />
<!-- 一级路由的出口 -->
<router-view></router-view>
</div>
<script>
const Home = { template: '<h3>首页</h3>' };
const Goods = { template: '<h3>商品列表</h3>' };
// 一级路由组件:内部再放一个 router-view 作为“二级路由”的出口
const NewsList = {
template: `
<div>
<div class="sub-nav">
<router-link to="/news/sport">体育新闻</router-link>
<router-link to="/news/ent">娱乐新闻</router-link>
<router-link to="/news/finance">财经新闻</router-link>
</div>
<router-view></router-view>
</div>
`
};
const Sport = { template: '<h3>体育新闻</h3>' };
const Ent = { template: '<h3>娱乐新闻</h3>' };
const Finance = { template: '<h3>财经新闻</h3>' };
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{
path: '/news',
component: NewsList,
// 方案二:访问 /news 时重定向到默认子路由,解决“没有默认选中项”
redirect: '/news/sport',
children: [
// 子路由 path 不带斜线,表示相对于父路由
{ path: 'sport', component: Sport },
{ path: 'ent', component: Ent },
{ path: 'finance', component: Finance }
]
},
{ path: '/goods', component: Goods }
]
});
new Vue({ el: '#app', router });
</script>
</body>
</html>关键行与易错点
children: [...]就是二级路由(子路由)的配置入口,它必须挂在某个一级路由配置项下面。- 子路由的
path写成'sport'(不带斜线)。加了斜线/sport就变成根目录下的路由,不再是/news/sport。 - 二级路由要显示出来,一级路由组件
NewsList的模板里必须有<router-view>,否则地址变了页面没反应。 - 解决“进入
/news不显示任何子页面”:用redirect: '/news/sport'重定向(转写稿里的方案二);方案一是把子路由 path 写成''并配exact,但容易和兄弟路由互相“高亮”,不如重定向干净。 - 转写稿提到的另一个坑:子组件如果写了
scoped样式,父级导航样式不会作用进去,router-link-active高亮会“失效”——把公共样式放到全局,或去掉scoped。
示例二:路由传递参数(动态路由 + 命名路由)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>路由传递参数</title>
<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>
<style>
body { font-family: sans-serif; }
.nav a { margin-right: 12px; }
li { margin: 6px 0; }
</style>
</head>
<body>
<div id="app">
<div class="nav">
<router-link to="/nowplaying">正在热映</router-link>
<router-link to="/comingsoon">即将上映</router-link>
</div>
<hr />
<router-view></router-view>
</div>
<script>
const films = [
{ id: 6445, name: '长安三万里' },
{ id: 6446, name: '封神第一部' }
];
const NowPlaying = {
data() {
return { films: films };
},
template: `
<div>
<h3>正在热映</h3>
<ul>
<li v-for="item in films" :key="item.id">
<!-- 写法一:字符串拼接 -->
<router-link :to="'/film/' + item.id">{{ item.name }}</router-link>
|
<!-- 写法二:对象写法(推荐,后面再带 query/params 都方便) -->
<router-link :to="{ name: 'filmDetail', params: { id: item.id } }">
对象写法看 {{ item.name }}
</router-link>
</li>
</ul>
</div>
`
};
const ComingSoon = { template: '<h3>即将上映</h3>' };
const FilmDetail = {
template: `
<div>
<h3>电影详情</h3>
<p>模板里取:{{ $route.params.id }}</p>
<p>JS 里取:{{ filmId }}</p>
</div>
`,
computed: {
// 组件被复用时(6445 → 6446)也能拿到最新参数
filmId() {
return this.$route.params.id;
}
}
};
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/nowplaying' },
{ path: '/nowplaying', component: NowPlaying },
{ path: '/comingsoon', component: ComingSoon },
// 动态路由参数:冒号后面的 id 会进入 $route.params
{ path: '/film/:id', name: 'filmDetail', component: FilmDetail }
]
});
new Vue({ el: '#app', router });
</script>
</body>
</html>关键行与易错点
- 路由配置里
:id是占位符,跳转时/film/6445的6445就进了this.$route.params.id。 :to="'/film/' + item.id"是字符串拼接写法;:to="{ name: 'filmDetail', params: { id } }"是命名路由写法——对象写法必须给路由配name,否则找不到。- 组件里取值用
this.$route.params.id($route是“当前路由信息”),不是$router($router是路由实例,用来push)。 - 易错点:从
/film/6445跳到/film/6446时组件会被复用,created不会重新执行,所以要在computed(或watch: { '$route'(){} })里取参数,不要只在created里存一次。 - 顺序易错点:
/film/nowplaying这类静态地址要写在/film/:id前面,否则会被当成id = nowplaying匹配掉。
示例三:用“一级 / 二级路由结构”控制导航显示与隐藏
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>隐藏导航(一级/二级路由结构方案)</title>
<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>
<style>
body { font-family: sans-serif; }
.nav a { margin-right: 12px; text-decoration: none; color: #333; }
.router-link-active { color: red; }
li { margin: 6px 0; }
</style>
</head>
<body>
<div id="app">
<!-- 只放一个出口:一级路由(详情页)直接在这里渲染,天然没有导航 -->
<router-view></router-view>
</div>
<script>
// 【一级路由】带导航的那一层:内部再开一个 router-view 给二级路由用
const Index = {
template: `
<div>
<div class="nav">
<router-link to="/index/nowplaying">正在热映</router-link>
<router-link to="/index/comingsoon">即将上映</router-link>
</div>
<hr />
<router-view></router-view>
</div>
`
};
const NowPlaying = {
template: `
<div>
<h3>正在热映</h3>
<ul>
<li><router-link to="/film/6445">长安三万里</router-link></li>
<li><router-link to="/film/6446">封神第一部</router-link></li>
</ul>
</div>
`
};
const ComingSoon = {
template: `
<div>
<h3>即将上映</h3>
<ul>
<li><router-link to="/film/7001">某部待映影片</router-link></li>
</ul>
</div>
`
};
// 【一级路由】没有导航:直接渲染在 App 的 router-view 里
const FilmDetail = {
template: '<h3>电影详情,id = {{ $route.params.id }}</h3>'
};
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/index/nowplaying' },
{
// 需要导航的组件,作为“地址为 /index 的一级路由”的二级路由
path: '/index',
component: Index,
redirect: '/index/nowplaying',
children: [
{ path: 'nowplaying', component: NowPlaying },
{ path: 'comingsoon', component: ComingSoon }
]
},
// 不需要导航的组件,作为一级路由
{ path: '/film/:id', component: FilmDetail }
]
});
new Vue({ el: '#app', router });
</script>
</body>
</html>关键行与易错点
- 转写稿里的三段式设计思想:① 没有导航的组件做一级路由(
/film/:id);② 一级路由地址写成/index这种带斜线的路径;③ 需要导航的组件做成这个一级路由的二级路由(/index/nowplaying、/index/comingsoon)。 - 详情页
/film/6445匹配的是一级路由,渲染在#app的<router-view>里,它的模板里没有导航,所以导航自然消失——不用写任何v-if。 - 如果把详情页写成
/index/film/:id(挂成Index的 children),那它就会渲染在Index的router-view里,导航又回来了,这是最容易写错的地方。 - 一级组件
Index里忘了写<router-view>,正在热映/即将上映就会“点了没反应”。 - 备选方案(转写稿里也讲过):给路由加
meta: { isShow: true },在 App 里写v-if="$route.meta.isShow"控制导航——缺点是每加一个路由都要改配置,不如上面按结构划分“改一个地方就够”。
常见错误
点了一级导航后,地址栏已经变成
/newsList/体育,但页面下半部分一片空白- 原因:父级组件(newsList)模板里只写了导航链接,没有留出二级路由的出口。
- 修法:在父组件模板末尾补
<router-view/>,并确认子路由确实挂在它的children里。
进入
/newsList时三个子导航全灰、没有任何默认选中项- 原因:当前地址与任何一条子路由的
path都不匹配(子路由都是/newsList/体育这类完整路径)。 - 修法:二选一——给父级加一条
redirect指向默认子路由;或让默认子路由的path写成空串/与父级同路径并加exact,避免它把其他子路由也“吃掉”。
- 原因:当前地址与任何一条子路由的
一级导航“新闻列表”在点击任意子项后依然高亮,甚至多个链接同时高亮
- 原因:父级
router-link的to与子路由前缀相同,模糊匹配把子路由也算作父级激活;或者子路由path误写成以/开头的绝对路径,导致匹配范围扩大。 - 修法:父级链接加
exact;子路由path去掉前导斜线,写成相对形式。
- 原因:父级
在子路由里写
to="体育",点击后跳到/体育而不是/newsList/体育- 原因:
to不加前导斜线时按“相对当前路由”解析,层级被抬高了一级。 - 修法:写完整绝对路径
/newsList/体育;或改用:to="{name:'体育'}",用 name 跳转不受路径拼接影响。
- 原因:
用 name 跳转控制台报
No match for {name: "xxx"}- 原因:路由配置里没写
name,或 name 值与模板中的字符串大小写/拼写不一致。 - 修法:逐条给路由记录补
name,模板统一用对象形式:to="{name:'体育'}",改路径时不必再改模板。
- 原因:路由配置里没写
详情页
/film/645能显示,但this.$route.params.id是undefined- 原因:
path里的动态段名(如:filmId)与取值时用的键名(params.id)不一致。 - 修法:命名一处定义、处处一致;取值前先
console.log(this.$route.params)确认键名。
- 原因:
点进电影详情后,顶部“正在热映 / 即将上映”导航还在
- 原因:详情被配成了带导航父路由的子路由;或者用
v-if硬编码判断$route.name === 'A' || 'B',每加一个页面就要改两处。 - 修法:把无导航页面提升为一级路由、把有导航的页面统一挂到
/的children下;若沿用显隐方案,则在meta上打标记统一判断,避免写死名单。
- 原因:详情被配成了带导航父路由的子路由;或者用
列表页正常,详情接口却 403 或返回空
- 原因:详情接口要求的请求头与列表接口不同(多一项或少一项),直接复用了列表的 header。
- 修法:对照 Network 面板逐项比对请求头,为详情单独设置;同时检查 URL 拼接的 id 是否为
undefined。
上映日期渲染成一长串数字,主演渲染成一堆
[object Object]或带逗号- 原因:接口返回的是秒级时间戳和对象数组,直接插值输出。
- 修法:时间戳用过滤器
new Date(v * 1000)再拼“x月x日 周x”;数组先map取name再join(' '),或封装成过滤器复用。
父组件加了
scoped后,子路由里的导航样式全部失效- 原因:
scoped给样式加了作用域属性,子组件节点匹配不到。 - 修法:把这段样式移到公共样式或父组件的非 scoped 区块。
- 原因:
练习
在
newsList下配置体育/娱乐/财经三条子路由,要求:直接访问/newsList时自动落到体育新闻,一级导航“新闻列表”在任意子页都精确高亮且不误伤其他链接。把上面三条子路由的跳转全部改为
name方式,并在体育新闻子组件里打印出当前路由的name、path、params,验证跳转与取值一致。改造卖座案例:把“正在热映 / 即将上映”做成
/的二级路由(导航放在承载它们的父组件里),把电影详情做成一级路由,验证进入详情后顶部导航消失、返回后导航恢复。编写一个日期过滤器,把接口返回的秒级时间戳格式化为“x月x日 周x”,并应用到即将上映列表;要求月份和日期不足两位时补零。
在正在热映/即将上映列表中,把
filmId拼进router-link的to跳转到详情页,详情组件根据 id 请求数据并渲染片名、海报、简介;要求切换不同电影时详情内容随之更新。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 二级路由的基本用法 | 24:18 |
| 2 | 搭建卖座基本路由 | 11:00 |
| 3 | 正在热映与即将上映 | 22:58 |
| 4 | 隐藏导航链接 | 20:53 |
| 5 | 二级路由的应用 | 18:12 |
小结
- 二级路由能不能显示,第一件事永远是检查父组件里有没有
<router-view/>,而不是先怀疑配置。 - 子路由
path用相对写法、一级链接加exact,能避免绝大多数“高亮错乱”和“跳错层级”问题。 - 相对路径虽然能跑通,但一旦嵌套加深就极易出错;实际项目优先用
name+ 对象形式跳转,路径改动不影响模板。 - “默认选中某个子项”本质是让地址与某条子路由匹配,
redirect是比改path更清晰的做法。 - 隐藏导航的两种方案里,
meta+v-if属于补丁式写法,每加页面要改多处;“无导航的做一级、有导航的挂到/下”属于结构式写法,扩展时只动路由表,更推荐。 - 路由传参和接口请求是两件事:先确认
params拿到手,再拿它去请求,否则报错会同时出现在两层,排查成本翻倍。 - 详情页这类接口往往与列表接口请求头不同,直接复用是常见坑,接入前先比对一次 Network 面板。
- 组件内展示数组和时间戳时,统一走
map/过滤器处理,别在模板里堆表达式——后期复用和改格式都省事。