主题
路由与搭建一个基本路由(Lesson25 1/3)
本页对应 Lesson25,共 7 段视频,约 1 小时 9 分。
学习目标
- 能够独立搭建 Vue3 基本路由,配置重定向与 404 通配路由,并通过地址访问、刷新页面验证组件在
router-view中正确渲染。 - 能够说明 Vue Router 4 与 Vue2 路由在 404 通配写法、history 模式配置及路由实例方法位置上的差异,并完成对应代码改造。
- 能够将路由配置信息从入口文件抽离到独立路由模块,正确导入导出并在入口中注册,验证路由功能不受影响。
- 能够使用动态
import()实现路由懒加载,并通过浏览器 Network 面板验证目标组件 JS 文件按需加载。 - 能够搭建二级路由,在父组件中放置出口并配置重定向与导航链接,验证 Home 下 News 与 Message 的切换和默认跳转。
- 能够调用接口数据渲染 Message 列表,并搭建三级路由,通过路由参数与嵌套出口验证详情页内容正确显示。
知识点
一、基本路由与项目接入
为什么需要:路由要解决的是“地址变化时,在指定位置渲染指定组件”。没有路由,单页应用只能靠条件渲染硬写,地址和界面无法对应。
怎么想:先明确一级路由:/home 显示 Home,/about 显示 About;再在主组件里放路由出口,让匹配到的组件从这里出现。
怎么做:先安装路由。转写稿中明确建议用 npm 或 yarn,不要用 cnpm,因为当时用 cnpm 下载的 vue-router 缺少插件,导致出口处组件不显示。安装后创建路由实例,并在入口挂载。
ts
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')ts
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router主组件里用 <router-link to="/home">Home</router-link> 做导航,用 <router-view /> 做出口。
易错点:入口文件不要一会儿 .js 一会儿 .ts 混用;router-view 是出口,不是普通组件;createWebHistory 对应 Vue2 的 history 模式,不是 hash 模式。
二、重定向、别名与404
为什么需要:用户直接访问 / 时要有默认页;访问不存在的地址时不能白屏,要有 404。别名则用于让同一组件拥有另一个访问地址。
怎么想:重定向是“地址 A 自动跳到地址 B”;别名是“地址 A 和地址 B 都能匹配同一路由记录”。
怎么做:重定向用 redirect。404 在 Vue Router 4 中不能照搬 Vue2 的 path: '*',转写稿中调试后明确要使用带正则的动态参数:
ts
import NotFound from '../views/NotFound.vue'
{
path: '/:pathMatch(.*)*',
component: NotFound
}访问 /abc、/abc/123 都能进入 404;访问 /home 仍匹配正常路由。别名沿用路由记录的 alias 字段,写法与 Vue2 一致;转写稿只强调“和之前一样”,没有展开演示。
易错点:404 路由通常放在路由表最后;不要继续写 path: '*';动态参数名可自定义,但正则部分要能匹配多段地址。
三、二级、三级子路由
为什么需要:Home 页面里还要切换 News、Message,这就是二级路由;Message 里再点某条消息看详情,就是三级路由。
怎么想:先写父组件,在父组件里再放一个 <router-view />;路由表中在父路由下加 children。
怎么做:一级 /home 下配置 news、message;message 下再配置 detail/:id。子路由 path 不要重复写父级完整路径,除非你明确需要绝对路径。
ts
{
path: '/home',
component: Home,
children: [
{ path: 'news', component: NewsList },
{ path: 'message', component: Message },
{ path: 'message/detail/:id', component: Detail }
]
}Home 里写 <router-link to="/home/news">News</router-link>、<router-link to="/home/message">Message</router-link>,Message 里再写详情链接和 <router-view />。
易错点:父组件没有 <router-view />,子路由不会显示;子路由 path 写成 /news 会变成根级路径;三级路由一般到详情层即可,不要无限嵌套。
四、路由传参与路由跳转
为什么需要:列表点详情要把 id 带过去;编程式导航则用于按钮、登录成功等场景。
怎么想:动态参数写在 path 里,如 detail/:id;跳转可用声明式 router-link,也可用编程式 router.push/replace/go/back/forward。
怎么做:模板里 <router-link to="/home/message/detail/1">详情</router-link>;脚本里可用 $router.push('/home')、$router.replace('/home')、$router.go(-1)、$router.back()、$router.forward()。接收参数用 $route.params.id。
易错点:$router 是跳转对象,$route 是当前路由信息;push 会新增历史记录,replace 不新增;go(-1) 是后退,go(1) 是前进。
五、懒加载与配置抽离
为什么需要:所有路由组件一次性打包会让首屏变大;把路由配置都塞在 main.ts 也不利于维护。
怎么想:懒加载是“访问到该路由时再加载对应组件”;配置抽离是把 routes 和 createRouter 放到 src/router/index.ts,入口只引入 router。
怎么做:转写稿中明确两种懒加载写法都可用:
ts
const Home = () => import('../views/Home.vue')
{
path: '/home',
component: () => import('../views/Home.vue')
}抽离后 main.ts 只保留 import router from './router' 和 app.use(router)。
易错点:懒加载路径要写对;抽离后记得 export default router,入口记得引入;不要同时保留两份路由配置。
六、与Vue2的差异和调试
为什么需要:Vue3 的 router 4 在 404、history 模式、路由实例方法上有变化,照搬 Vue2 会报错。
怎么想:Vue2 的 mode: 'history' 对应 Vue3 的 history: createWebHistory();Vue2 的 path: '*' 对应 Vue3 的正则参数;$router 上的方法在 Vue3 中也可从 router 实例访问。
怎么做:转写稿中通过控制台输出 router 对象,看到 back、forward、beforeEach、addRoute、hasRoute、removeRoute、getRoutes 等方法。调试时先看地址是否匹配、再看出口是否存在、最后看组件是否引入正确。
易错点:404 不生效先检查是否用了正则参数;子路由不显示先检查父级 <router-view />;组件不显示先检查安装方式与入口挂载。
先明确目标:基本路由要解决什么
为什么需要:页面要根据地址切换组件,例如访问 /home 显示 Home,访问 /about 显示 About。没有路由时,只能手写条件判断或整页跳转。
怎么想:先准备页面组件,再配置“地址 → 组件”的映射,最后把 router 安装到 Vue 应用,并在 App 中放出口。
怎么做:本节按“下载 vue-router → 写组件 → 配路由表 → 创建 router → 安装 → 放 router-view”推进。
易错点:转写稿特别提醒,用 cnpm 下载 vue-router 时曾出现缺少插件、router-view 出口不渲染的问题;建议用 npm 或 yarn 安装。先保证依赖正常,再排查路由配置。
安装 vue-router
为什么需要:Vue3 的路由能力由 vue-router 提供。
怎么想:项目若是默认模板创建,通常没有自动装路由;需要手动安装。
怎么做:
shell
yarn add vue-router | npm install vue-router易错点:不要混用包管理器导致依赖树异常;安装后先确认 package.json 中有 vue-router。
在 main.ts 中配置基本路由
为什么需要:路由表、历史模式、滚动行为、404 都要在入口统一创建。
怎么想:先引入 createRouter 和 history 工厂,再写 routes,最后 app.use(router)。
怎么做:保留本节代码:
ts
import {createApp} from "vue";
import App from "@/App.vue";
import Home from "@/pages/Home/index.vue";
import About from "@/pages/About/index.vue";
import NotFount from "@/pages/NotFount/index.vue";
// 1- 引入createRouter
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
// 2- 配置每一个路由器的信息
const routes = [
{
path: "/",
redirect: "/home"
}, {
path: "/home",
component: Home
},
{
path: "/about",
component: About
},
{
// vue-router@4不支持以下代码实现404
// path:"*",
// component:NotFount
// 需要借助params形式实结合正则实现404
path: "/:pathMath(.*)",
component: NotFount
}
]
// 3- 创建router
const router = createRouter({
// 相当于Vue2当中的mode:"history"
history: createWebHistory(),
// 相当于Vue2当中的mode:"hash"
// history:createWebHashHistory(),
routes,
scrollBehavior() {
return {
left: 0,// 相当于之前的x
top: 0// 相当于之前的y
}
}
});
// 4- 安装1
// const app = createApp(App);
// app.use(router);
// app.mount("#app");
// 5- 安装2-链式调用
createApp(App).use(router).mount("#app");易错点:createWebHistory 对应 Vue2 的 history 模式,createWebHashHistory 对应 hash 模式;scrollBehavior 在 Vue3 中返回 left/top,不是旧写法的 x/y。转写稿中就是先把 x/y 改成 left/top 才消除警告。
404 与 Vue2 的差异
为什么需要:访问不存在地址时要显示 NotFount,而不是空白。
怎么想:Vue2 常写 path:"*",但 vue-router@4 不再支持。
怎么做:用 params 加正则:path: "/:pathMath(.*)",让任意未匹配路径进入 404 组件。
易错点:转写稿中直接写 * 会报错,提示需要自定义正则;改成上述写法后,访问 /1、/2、/3 都能进入 404。参数名可自定义,但必须和正则形式配合。
把路由配置外置
为什么需要:所有配置都堆在 main.ts 会越来越乱。
怎么想:把 routes 和 createRouter 放到 src/router/index.ts,导出 router,入口只负责引入和安装。
怎么做:新建 src/router/index.ts,把组件引入、路由表、createRouter 移过去;main.ts 中 import router from "@/router",再 app.use(router)。
易错点:外置后必须 export default router,入口要改引入路径;否则会出现找不到 router 或重复创建。
懒加载
为什么需要:首屏不必把所有页面组件都下载,按访问需要加载。
怎么想:把静态 import Home from ... 改成动态 import()。
怎么做:component: () => import("@/pages/Home/index.vue"),也可以先定义再使用。
易错点:转写稿中通过 Network 观察,切到 About 才加载 About 的 JS;若仍静态引入,就不会有按需加载效果。
二级、三级路由与出口
为什么需要:Home 下还要有 News、Message,Message 下还要有详情。
怎么想:父路由加 children,父组件模板中再放一个 router-view 作为子出口;导航用 router-link 的 to。
怎么做:一级 /home 下配 children,如 news、message;message 下再配三级 children。父级可写 redirect 到默认子路由。
易错点:转写稿调试时出现“点了没内容”,原因是缺少子级 router-view;还出现少写 news/list 路径导致匹配失败。二级、三级路由必须同时检查“路由表 children”和“组件出口”两处。
列表渲染简记
为什么需要:Message 页要展示接口返回的列表。
怎么想:请求数据后放进响应式变量,再用 v-for 渲染。
怎么做:转写稿用 axios 请求,得到 res.data.data 一类结构后交给 ref/reactive;模板中 v-for 加 key。
易错点:转写稿提到不要把代理对象直接替换成普通数组/对象,否则响应式丢失;用 ref 时注意通过 .value 赋值。
为什么要将路由配置移出 main.ts
为什么需要:如果所有路由相关代码都写在 main.ts 里,入口文件会同时承担“创建应用、配置路由、安装路由、挂载应用”多件事。路由一多,main.ts 会越来越长,既不便于维护,也不便于团队协作。转写稿中也提到:“把所有东西都写在 main.ts 里面,是不是不太合适”,所以要把创建 router 相关部分剪切到外部文件。
怎么想:入口文件只负责“启动应用”,路由配置交给专门的 src/router/index.ts。这样职责清晰:router/index.ts 负责引入页面组件、定义 routes、创建并导出 router;main.ts 只负责引入 router 并安装。
怎么做:在 src/router/index.ts 中集中写路由配置,并 export default router。
ts
import Home from "@/pages/Home/index.vue";
import About from "@/pages/About/index.vue";
import NotFount from "@/pages/NotFount/index.vue";
// 1- 引入createRouter
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
// 2- 配置每一个路由器的信息
const routes = [
{
path: "/",
redirect: "/home"
}, {
path: "/home",
component: Home
},
{
path: "/about",
component: About
},
{
// vue-router@4不支持以下代码实现404
// path:"*",
// component:NotFount
// 需要借助params形式实结合正则实现404
path: "/:pathMath(.*)",
component: NotFount
}
]
// 3- 创建router
const router = createRouter({
// 相当于Vue2当中的mode:"history"
history: createWebHistory(),
// 相当于Vue2当中的mode:"hash"
// history:createWebHashHistory(),
routes,
scrollBehavior() {
return {
left: 0,// 相当于之前的x
top: 0// 相当于之前的y
}
}
});
export default router;易错点:@ 别名要能正确指向 src;页面组件路径要写全,如 @/pages/Home/index.vue;createRouter 必须同时传 history 和 routes;Vue Router 4 中 404 不要再用 path: "*",笔记里采用 /:pathMath(.*) 结合正则匹配;scrollBehavior 返回的是 left/top,不是 Vue2 里的 x/y。
在 main.ts 中引入并安装路由
为什么需要:路由配置虽然已经抽到外部,但应用启动时仍然要“安装”路由,否则 <router-view>、<router-link> 等能力不会生效。
怎么想:router/index.ts 默认导出了 router,main.ts 只需要 import router from "@/router",然后通过 app.use(router) 安装。转写稿中强调:“在 main.ts 里面要使用,得把它引入进来;引入进来还不行,你得把它导出去。”
怎么做:保留两种等价写法。第一种是分开写,便于阅读;第二种是链式调用。
ts
import {createApp} from "vue";
import App from "@/App.vue";
import router from "@/router";
// 4- 安装1
// const app = createApp(App);
// app.use(router);
// app.mount("#app");
// 5- 安装2-链式调用
createApp(App).use(router).mount("#app");易错点:import router from "@/router" 对应的是 src/router/index.ts 的默认导出,不要写成具名导入;不要在 main.ts 里重复写 routes;app.use(router) 要写在 mount 之前;如果采用链式调用,注意 use 返回的仍是 app 实例,最后再 mount。
调试与验证
为什么需要:把配置移到外部后,容易出现“路径对不上、组件不显示、导出没接上”的问题,需要验证入口文件和路由文件是否真正连起来。
怎么想:先看 main.ts 是否引入了 router 并 use,再看 router/index.ts 是否默认导出,最后看地址是否匹配 routes。转写稿中也通过控制台查看 router 对象,确认其中有 push、replace、go、back、forward、addRoute 等方法。
怎么做:启动项目后访问 /home、/about,再输入一个不存在的地址验证 404;点击导航时观察页面是否切换。若后续要把组件引入改成懒加载,可把 import Home from ... 改成 () => import(...),这是外部配置后常见的扩展方式,但本节先保证基础配置跑通。
易错点:文件后缀要与项目配置一致,转写稿提醒“选了 TS 就不要把 .js 当 .ts 用”;导出和引入要成对;404 路由要放在普通路由之后,避免抢先匹配。
从基本路由理解懒加载的位置
为什么需要:路由的本质是“地址变化时,在指定出口渲染指定组件”。如果所有页面组件都在入口处静态 import,打包后主包会变大,首屏要等所有页面 JS 都下载完。懒加载就是让某个路由组件在被访问时再加载对应 JS。
怎么想:先搭好基本路由:createRouter 创建实例,routes 配置地址与组件,router-view 提供出口,router-link 负责跳转。然后把“页面组件”的导入方式从静态改为动态函数。
怎么做:本节笔记把路由配置放在 src/router/index.ts:
ts
import NotFount from "@/pages/NotFount/index.vue";
// 1- 引入createRouter
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
const Home = ()=>import("@/pages/Home/index.vue");
// 2- 配置每一个路由器的信息
const routes = [
{
path: "/",
redirect: "/home"
}, {
path: "/home",
component: Home
},
{
path: "/about",
component: ()=>import("@/pages/About/index.vue")
},
{
// vue-router@4不支持以下代码实现404
// path:"*",
// component:NotFount
// 需要借助params形式实结合正则实现404
path: "/:pathMath(.*)",
component: NotFount
}
]
// 3- 创建router
const router = createRouter({
// 相当于Vue2当中的mode:"history"
history: createWebHistory(),
// 相当于Vue2当中的mode:"hash"
// history:createWebHashHistory(),
routes,
scrollBehavior() {
return {
left: 0,// 相当于之前的x
top: 0// 相当于之前的y
}
}
});
export default router;易错点:component 要写“返回 import 的函数”,不要写成直接执行 import 的结果;Home 这种先定义再使用、about 这种内联写法都可以。安装 vue-router 时转写稿特别提醒:用 cnpm 可能缺少插件,导致 router-view 出口组件不显示,建议用 npm 或 yarn 安装。
路由实例要接入应用并留好出口
为什么需要:只创建 router 不会自动生效,必须在入口应用里 app.use(router),并在父组件放 router-view。
怎么想:转写稿的推导是:先 createApp(App),再 app.use(router),最后 app.mount("#app")。也可以把 createApp 的结果先赋给常量再挂载。
怎么做:在 main.ts 中引入外部路由文件并注册;页面中用 <router-view> 显示匹配组件,用 <router-link to="/home"> 跳转。
易错点:忘记 app.use(router) 时,router-link、router-view 不工作;router-view 放错层级时,二级、三级路由没有出口。
404 与滚动行为:Vue Router 4 的差异
为什么需要:用户输入不存在地址时要有兜底页面;切换路由后希望回到页面顶部。
怎么想:Vue Router 4 不支持旧写法 path:"*"。转写稿的调试过程是:先按 Vue2 写 * 不生效,后来改为携带自定义参数并结合正则匹配任意路径。
怎么做:笔记使用 path: "/:pathMath(.*)" 匹配任意路径,再指向 NotFount。滚动行为中 left 对应旧版 x,top 对应旧版 y,返回 { left: 0, top: 0 }。
易错点:不要直接把 Vue2 的 mode:"history" 搬过来;Vue Router 4 用 createWebHistory() 或 createWebHashHistory()。404 参数名可自定义,但正则写法要对齐当前版本。
把路由配置抽到外部,观察懒加载效果
为什么需要:所有配置都塞在 main.ts 里不利于维护。转写稿把路由相关代码抽到 src/router/index.ts,再在入口引入。
怎么想:抽离后,main.ts 只负责创建应用、注册路由、挂载;路由文件负责引入组件、配置 routes、导出 router。
怎么做:router/index.ts 末尾 export default router;main.ts 中 import router from "./router",再 app.use(router)。调试时打开 Network 面板,点击 about 或 home,能看到对应页面的 JS 被单独请求;未访问的页面不会一开始就加载。
易错点:路径别名 @ 要确保配置正确;抽离后忘记导出或忘记引入,会出现路由实例找不到。转写稿还提到项目用 TS 时,创建文件不要混写 .js 当 .ts 用。
懒加载的两种写法与验证
为什么需要:让路由组件按需加载,减少首屏压力。转写稿的验证方式是:先只访问 home,Network 里只加载 home 相关资源;再点击 about,才加载 about 的资源。
怎么想:静态导入 import NotFount from ... 会进入主包;懒加载则把 import() 包在函数里,路由匹配时才执行。
怎么做:可以像 const Home = () => import("@/pages/Home/index.vue") 先定义再使用,也可以像 component: () => import("@/pages/About/index.vue") 直接写在路由项里。
易错点:不要把懒加载写成 component: import(...);不要以为写了懒加载就一定会拆包,构建工具和路径都要正常。二级、三级路由同样可以懒加载,但父级必须有 router-view 出口,否则子路由匹配了也显示不出来。
二级路由要解决什么问题
一级路由让 /home 显示 Home 组件,但 Home 内部还要在 NewsList 和 Message 之间切换。如果每次切换都整页换组件,Home 的导航和布局会被销毁;二级路由就是让“Home 内部再有一个出口”,只替换 Home 里的内容。转写稿里说的“二级左右”应纠正为“二级路由”,配置思路和 Vue2 一样:在父路由里加 children。
怎么想:先有父级 /home,再让 /home/newsList、/home/message 成为它的子路由;父组件 Home 里必须放 router-view 作为二级出口。易错点:只配 children 不写 router-view,子组件不会显示;子路由地址和父级关系要理清。
先创建两个二级页面组件
为什么需要:路由配置最终要指向组件,所以先把 NewsList 和 Message 建出来。怎么做:在 src/pages/Home 下分别建 NewsList/index.vue 和 Message/index.vue。笔记代码如下:
vue
<!-- src/pages/Home/NewsList/index.vue -->
<template>
<h3>NewsList</h3>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>vue
<!-- src/pages/Home/Message/index.vue -->
<template>
<div>
<div>
<ul>
<li>
<a href="/home/message/1">message001</a>
<button>push</button>
<button>replace</button>
</li>
<li><a href="/home/message/2">message002</a> <button>push
</button> <button>
replace
</button>
</li>
<li><a href="/home/message/4">message004</a> <button>push
</button> <button>
replace
</button>
</li>
</ul>
<button>前进</button>
<button>后退</button>
</div>
</div>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>易错点:这里按钮只是占位,笔记没有绑定事件;不要误以为已经实现 push/replace/前进/后退。原生 <a href> 会整页刷新,后续应改为 router-link 或编程式导航。
在 Home 中放二级导航和出口
为什么需要:Home 是父级页面,它要知道“点 News 显示谁、点 Message 显示谁”,还要提供子组件出现的位置。怎么想:导航用 router-link,出口用 router-view。怎么做:修改 src/pages/Home/index.vue:
vue
<template>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li><router-link to="/home/newsList" class="list-group-item">News</router-link></li>
<li><router-link to="/home/message" class="list-group-item" >Message</router-link>
</li>
</ul>
<router-view/>
</div>
</template>易错点:router-link 用 to,不是 href;router-view 不能漏。转写稿里“roer到位”就是 router-view 的口误。
在路由表里配置 children 和默认重定向
为什么需要:让 /home 的子地址真正映射到组件,并让进入 /home 时默认显示 NewsList。怎么想:父路由 /home 下加 children;默认地址用重定向。怎么做:在 src/router/index.ts 中配置:
ts
{
path: "/home",
component: Home,
children:[
{
path:"/home",
redirect:"/home/newsList"
},
{
path:"/home/message",
component:()=>import("@/pages/Home/Message/index.vue")
},
{
path:"/home/newsList",
component:()=>import("@/pages/Home/NewsList/index.vue")
}
]
}易错点:笔记中子路由写的是完整路径,所以 path 以 /home 开头;若改相对写法,就不要以 / 开头。重定向项要和父级 /home 对应,否则进入 Home 后不会自动落到 NewsList。转写稿里“重新向/重名香”应纠正为“重定向”。调试时先看地址是否变成 /home/newsList,再看 Home 里是否有二级 router-view。
本节目标与代码位置
本节要完成的是:在 src/pages/Home/Message/index.vue 中,通过接口拿到电影列表数据,再用 v-for 渲染到页面上。笔记里保留了安装 axios 的命令和完整组件代码。转写稿里反复说的“麦值 / meage”就是 Message,“费”就是 film,这里统一纠正为 Message、films。
本节的重点不是路由跳转本身,而是先把 Message 页面里的列表数据渲染出来;push、replace、前进、后退按钮先放在模板里,后续再接入编程式导航。
为什么需要接口渲染列表
为什么需要:真实列表通常来自后端接口,不能把电影名一条条写死在模板里。转写稿里也提到,可以先写假数据确认结构,但最终数据要来自接口。
怎么想:先确认页面要渲染什么字段。这里列表项需要 item.name 和唯一的 item.filmId,所以接口返回的 films 数组应该直接保存到响应式数据里。
怎么做:安装 axios:
shell
cnpm install axios易错点:转写稿里提到 cnpm、npm 都可以尝试,但笔记给定命令是 cnpm install axios,按笔记执行即可;不要把 axios 安装到错误目录。
怎么想:请求配置与返回层级
为什么需要:这个接口不是随便 get 就能返回数据,它要求携带 X-Client-Info 和 X-Host 请求头。转写稿里调试时也是先打开网络/控制台确认请求头和返回结构。
怎么想:先把请求头按接口要求原样放进 headers,其中 X-Client-Info 的值是一个 JSON 字符串,必须用引号包住。请求成功后再看返回层级:value.data.data.films。
怎么做:在 onMounted 中发请求,并把 films 赋给 state.films。组件代码如下:
vue
<template>
<div>
<div>
<ul>
<li v-for="item in state.films" :key="item.filmId">
<a href="/home/message/1">{{item.name}}</a>
<button>push</button>
<button>replace</button>
</li>
</ul>
<button>前进</button>
<button>后退</button>
</div>
</div>
</template>
<script lang="ts" setup>
import {onMounted, reactive} from "vue";
import axios from "axios";
let state = reactive({
films:[]
})
onMounted(function(){
// https://m.maizuo.com/gateway?cityId=310100&pageNum=1&pageSize=10&type=1&k=6086743
axios.get('https://m.maizuo.com/gateway?cityId=310100&pageNum=1&pageSize=3&type=1&k=6086743',{
headers:{
"X-Client-Info":'{"a":"3000","ch":"1002","v":"5.2.1","e":"16923264631851757969801217","bc":"310100"}',
"X-Host":"mall.film-ticket.film.list"
}
}).then(value=>{
// console.log(value.data.data.films);
state.films = value.data.data.films;
})
})
</script>
<style scoped>
</style>易错点:不要写成 value.data.films,转写稿调试时专门打印过 value.data.data.films;headers 的键名和字符串格式要保留,X-Client-Info 不是普通对象,不能漏引号。
怎么做:响应式数据与 v-for 渲染
为什么需要:接口数据回来后,模板要能自动更新,所以用 reactive 包一个 state,把 films 放进去。
怎么想:state.films = value.data.data.films 是给响应式对象的属性重新赋值,这是可以的;不要把整个 state 重新赋值成普通对象。模板里 v-for="item in state.films",key 用 item.filmId,因为它是唯一标识。
怎么做:先确认 v-for 写在 li 上,列表项显示 item.name;a 标签里的 /home/message/1 目前是占位地址,后续再改成动态路由。push、replace、前进、后退按钮暂时不写逻辑。
易错点:key 不要用 index 代替唯一 filmId;如果接口没返回或层级写错,state.films 会是空数组,页面就不显示列表。转写稿里也提醒过:先 console.log 看数据,再赋值渲染,这是最稳的调试过程。
调试过程与本节小结
转写稿里的调试顺序可以整理为:先写模板和假数据确认 v-for 能渲染;再安装 axios;在 onMounted 中请求接口;在 then 里打印 value.data.data.films;确认是数组后赋值给 state.films;最后刷新页面检查列表。若列表不出现,优先检查请求头、接口返回层级、state.films 是否被正确赋值。
本节完成后,Message 页面已经能渲染电影列表。下一步才是在 a 标签和按钮上接入路由跳转、push/replace 以及前进后退。
从二级列表到三级详情:为什么需要三级路由
前面已经完成了一级路由和二级路由:/home 是一级,/home/message、/home/news 是二级。现在 Message 页面里渲染的是电影列表,点击某一部电影后,需要看到这部电影的详情。此时只靠二级路由不够,因为 URL 必须能表达“我看的是哪一部电影”,否则刷新、复制链接、前进后退都无法定位到具体详情。
所以这里要加的是三级路由:父级是 Message 列表,子级是某部电影的 Detail 详情。转写稿里的思路也是先把一级、二级跑通,再渲染 Message 列表,最后才搭三级路由。三级路由不是新的 API,仍然是把组件配到 children 里,只是层级更深。也提到:一般项目里二级路由较多,三级也有,但比三级还深的几乎就没了。
先建三级组件:Detail/index.vue
先在 src/pages/Home/Message/Detail/index.vue 建详情组件。笔记里的代码如下,保留原样:
vue
<template>
<h3>Detail</h3>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>为什么先建组件?因为路由配置里要 import 它,组件不存在时,路由即使配了也会在加载时报错。怎么想?三级路由的组件应该放在二级 Message 目录下面,形成 Home -> Message -> Detail 的层级,便于维护。怎么做?先只写一个 <h3>Detail</h3> 占位,确认路由能通,再考虑用 filmId 请求详情。易错点:文件名和路径大小写要一致;index.vue 不要写成 Index.vue;script setup 先空着没问题,但不要漏掉模板。
在二级路由的 children 里挂三级路由
接着改 src/router/index.ts,在 /home/message 的 children 中增加三级路由。笔记代码如下:
ts
{
path:"/home/message",
component:()=>import("@/pages/Home/Message/index.vue"),
children:[
{
path:"/home/message/:filmId",
component:()=>import("@/pages/Home/Message/Detail/index.vue")
}
]
}为什么这样配?/home/message 仍然是二级列表页,/home/message/:filmId 是它的子路由,:filmId 是动态段,用来接收电影 ID。怎么想?三级路由要挂在二级路由的 children 数组里,而不是再写一个平级的一级路由。怎么做?把 Detail 的 component 用 ()=>import(...) 懒加载进来,和二级路由写法一致。易错点:path 里的参数名要和跳转时一致,比如都用 filmId;如果写成 /home/message/:id,详情里就要取 params.id。笔记里写的是绝对路径 /home/message/:filmId,在 children 中也能匹配,但心里要清楚它最终拼出的地址就是 /home/message/具体ID。
出口与跳转:三级路由怎么显示
路由配好后,地址变了不代表页面一定出来。转写稿里配完三级路由后先发现“没有指定出口”,然后在 Message 组件里补了 router-view,三级详情才显示。原因是:二级路由 Message 是父级,三级 Detail 要显示在 Message 组件内部的出口位置,所以 Message/index.vue 的模板里必须有 <router-view></router-view>。如果没有这个出口,URL 虽然变成 /home/message/1,页面仍然只显示列表。
跳转时,列表项要指向具体电影,例如把 to 写成 /home/message/${item.filmId} 这种形式;进入 Detail 后,用 useRoute().params.filmId 取参数。这里要注意:取参数用 useRoute,做跳转用 useRouter,两个不要混。易错点还有:父级没有 router-view;跳转地址少了 /home/message/ 前缀;参数名写错;详情组件里把 params 当成 query。另外,Vue Router 4 里 404 不再直接写 path:"*",转写稿里调试时提示要使用带自定义正则的 pathMatch,这是和 Vue2 路由不同的地方;本节先把三级路由跑通,再处理这些差异。
调试顺序与小结
建议按这个顺序练:先确认 /home/message 能显示列表;再建 Detail/index.vue;然后在 router/index.ts 的 children 里加 :filmId 路由;接着在 Message/index.vue 里加 router-view;最后把列表项跳转地址拼上 filmId,点进去看详情是否出现。调试时重点看三处:路由配置的层级、父组件的出口、跳转地址和参数名是否一致。三级路由本质还是“父路由里套子路由”,把这三处对齐,Home -> Message -> Detail 就能正常呈现。
课堂画面






代码示例
代码一:搭建基本路由(含 404、滚动行为、懒加载、路由抽离)
ts
// src/router/index.ts
import NotFount from "@/pages/NotFount/index.vue";
// 1- 引入 createRouter
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
// 懒加载:先定义再使用
const Home = () => import("@/pages/Home/index.vue");
// 2- 配置每一个路由器的信息
const routes = [
{
path: "/",
redirect: "/home"
}, {
path: "/home",
component: Home
},
{
path: "/about",
component: () => import("@/pages/About/index.vue")
},
{
// vue-router@4 不支持 path:"*" 实现 404
// 需要借助 params 形式结合正则实现 404
path: "/:pathMath(.*)",
component: NotFount
}
]
// 3- 创建 router
const router = createRouter({
// 相当于 Vue2 当中的 mode:"history"
history: createWebHistory(),
// 相当于 Vue2 当中的 mode:"hash"
// history:createWebHashHistory(),
routes,
scrollBehavior() {
return {
left: 0, // 相当于之前的 x
top: 0 // 相当于之前的 y
}
}
});
export default router;ts
// src/main.ts
import {createApp} from "vue";
import App from "@/App.vue";
import router from "@/router";
// 4- 安装方式一
// const app = createApp(App);
// app.use(router);
// app.mount("#app");
// 5- 安装方式二:链式调用
createApp(App).use(router).mount("#app");要点与易错点
- 路由配置抽到
src/router/index.ts后必须export default router,main.ts里再import router from "@/router",否则app.use(router)拿不到实例。 - 404 写法:
path: "/:pathMath(.*)"必须配(.*)正则,只写:pathMath只能匹配一个参数段(/a行,/a/b就匹配不上)。 vue-router@4不再支持path: "*",照搬 Vue2 写法会直接报错。scrollBehavior返回的键是left / top,不是 Vue2 的x / y。createWebHistory()与createWebHashHistory()二选一,等价于 Vue2 的mode: "history" / "hash"。- 转写稿里特别提醒:用
cnpm安装vue-router会缺插件,router-view处组件渲染不出来,建议用npm install vue-router或yarn add vue-router。
代码二:搭建二级路由(父组件出口 + children 配置)
vue
<!-- src/pages/Home/index.vue -->
<template>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li><router-link to="/home/newsList" class="list-group-item">News</router-link></li>
<li><router-link to="/home/message" class="list-group-item">Message</router-link></li>
</ul>
<router-view/>
</div>
</template>ts
// src/router/index.ts —— Home 的 children 部分
{
path: "/home",
component: Home,
children: [
{
path: "/home",
redirect: "/home/newsList"
},
{
path: "/home/message",
component: () => import("@/pages/Home/Message/index.vue")
},
{
path: "/home/newsList",
component: () => import("@/pages/Home/NewsList/index.vue")
}
]
}要点与易错点
- 二级组件要显示,父组件
Home/index.vue模板里必须有<router-view/>作为出口,少了它页面只有导航没有内容。 children里的path写完整路径(/home/message)或相对路径都可以,写相对路径时不要再带开头的/。{ path: "/home", redirect: "/home/newsList" }让进入/home时自动落到默认二级页,否则父级只有导航是空壳。- 导航改用
<router-link to="...">而不是<a href>,<a>会整页刷新,丢掉 SPA 的路由切换效果。
代码三:渲染列表 + 三级路由(params 传参)
vue
<!-- src/pages/Home/Message/index.vue -->
<template>
<div>
<div>
<ul>
<li v-for="item in state.films" :key="item.filmId">
<a href="/home/message/1">{{item.name}}</a>
<button>push</button>
<button>replace</button>
</li>
</ul>
<button>前进</button>
<button>后退</button>
</div>
<!-- 三级路由出口 -->
<router-view/>
</div>
</template>
<script lang="ts" setup>
import {onMounted, reactive} from "vue";
import axios from "axios";
let state = reactive({
films: []
})
onMounted(function () {
axios.get('https://m.maizuo.com/gateway?cityId=310100&pageNum=1&pageSize=3&type=1&k=6086743', {
headers: {
"X-Client-Info": '{"a":"3000","ch":"1002","v":"5.2.1","e":"16923264631851757969801217","bc":"310100"}',
"X-Host": "mall.film-ticket.film.list"
}
}).then(value => {
state.films = value.data.data.films;
})
})
</script>ts
// src/router/index.ts —— Message 的 children(三级路由)
{
path: "/home/message",
component: () => import("@/pages/Home/Message/index.vue"),
children: [
{
path: "/home/message/:filmId",
component: () => import("@/pages/Home/Message/Detail/index.vue")
}
]
}要点与易错点
- 三级路由同样嵌套在二级的
children里,二级组件Message/index.vue必须再写一个<router-view/>,否则Detail不会出现。 :filmId是 params 传参,Detail组件里用useRoute().params.filmId取,注意是useRoute(取当前路由对象),不是useRouter(拿路由器做跳转)。- 响应式赋值要整体赋值
state.films = value.data.data.films;如果写成let films = reactive([])再films = xxx,就把代理对象替换成了普通数组,视图不会更新。 axios返回的是响应对象,真实数据在value.data.data.films(两层data),少一层就取到undefined。- 请求头
X-Client-Info是 JSON 字符串,外面必须用单引号包;X-Host里有特殊字符用双引号,写成对象字面量会导致语法错误。 - 列表
:key用item.filmId,不要用index,否则列表更新时复用错节点。
常见错误
现象:页面能启动,但
<router-view/>位置始终不渲染组件。
原因:使用cnpm安装vue-router后依赖/插件不完整,路由出口组件未正常生效。
修法:删除node_modules与锁文件,改用npm/yarn/pnpm重新安装,并确认app.use(router)已执行。现象:访问不存在地址时没有进入 404 页面。
原因:Vue Router 4 不支持 Vue Router 3 的path: "*"写法。
修法:使用path: "/:pathMatch(.*)*",并放在路由表最后;注意参数名是pathMatch,不是pathMath。现象:
scrollBehavior返回{ x: 0, y: 0 }后滚动行为不生效。
原因:Vue Router 4 的滚动位置字段改为left/top。
修法:返回{ left: 0, top: 0 }。现象:把路由配置抽到
src/router/index.ts后页面空白或路由不生效。
原因:外部路由文件没有export default router,或main.ts没有import router from "@/router"。
修法:路由文件导出 router 实例,入口文件正确引入并use(router)。现象:访问二级路由时父组件正常,子组件不显示。
原因:父组件模板缺少<router-view/>,或children配置未挂到对应父路由下。
修法:在父组件需要渲染子路由的位置补<router-view/>,并检查children层级与路径匹配。现象:嵌套路由中父级导航一直高亮,
exact不生效。
原因:父路由路径是子路由路径前缀,普通 active class 会持续匹配。
修法:用route.matched/ 当前路径精确判断,或自定义 class 控制高亮,不依赖exact。现象:懒加载路由首次访问报模块找不到。
原因:component: () => import("...")路径写错,或把组件实例/变量误写成非函数。
修法:确认import()内为可解析路径,懒加载项必须是返回 Promise 的函数。
练习
- 将当前基本路由拆到
src/router/index.ts,在main.ts中只保留引入与安装,并验证首页重定向、404、滚动行为都正常。 - 把
Home、About改为懒加载,打开 Network 面板验证首次进入只加载当前路由对应分包。 - 为
Home增加NewsList、Message二级路由,配置默认重定向,并给Message增加/:filmId三级详情路由。 - 在
Message列表中实现push、replace、前进、后退跳转,并观察浏览器历史记录变化。 - 人为制造一次“父组件缺少
<router-view/>”和一次“404 写成path:"*"”的问题,记录控制台/页面现象后修复。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 实现基本路由 | 25:00 |
| 2 | 配置404与Vue2的差异 | 08:22 |
| 3 | 将路由配置信息放置在外部 | 03:20 |
| 4 | 懒加载 | 02:42 |
| 5 | 搭建二级路由 | 11:45 |
| 6 | 渲染Message列表 | 13:18 |
| 7 | 三级路由 | 04:50 |
小结
- Vue Router 4 的核心入口是
createRouter,历史模式用createWebHistory/createWebHashHistory明确选择。 - 404 捕获必须使用 Vue Router 4 的参数正则写法,且要放在路由表末尾。
scrollBehavior在 v4 中使用left/top,不要继续沿用 v3 的x/y。- 路由配置外置后,导出、引入、安装三步缺一不可,否则表现为空白或导航无效。
- 懒加载的正确形态是
component: () => import("..."),可用于验证分包和按需加载。 - 嵌套路由能否显示,关键看父组件是否提供
<router-view/>,路径层级只决定匹配。 - 嵌套导航高亮不要迷信
exact,应基于当前路由或matched做精确控制。 - 安装依赖优先使用
npm/yarn/pnpm,遇到路由出口异常先排查依赖安装方式与app.use(router)。