# 路由基础
路由的切换其实就是切换 div, 前提是看页面地址变不变化
路由相关的信息如果跟页面挂钩了,如果路由改变的话页面也会改变
# 单页面应用
-
不会新建一个窗口,不同内容在地址栏中改变 (查看),但不刷新当前页面
-
vue-router 使用 npm i vue-router 安装
# 如何制作和添加
-
src 文件夹下自创的全局 router.js
// 创建路由 import Vue from "vue"; import VueRouter from "vue-router"; import Home from "./components/Home.vue"; import Pins from "./components/Pins.vue"; import Books from "./components/Books.vue"; // 1.需要将路由功能制作成 vue 中的全局功能,需要使用 Vue 的use方法 Vue.use(VueRouter); // 将所有的路由组件制作成了全局组件 // 2.制作路由(一个路由代表一个页面(是一个对象)) // 1). 创建路由数组 // 路由的地址不要写成大写的 const routes = [ { path: "/", //代表根地址 http://localhost:8080/ component: Home, }, { path: "/pins", // 代表 http://localhost:8080/pins component: Pins, }, { path: "/books", // 代表 http://localhost:8080/books component: Books, }, ]; // 2). 根据路由数组创建 整体的路由 const router = new VueRouter({ routes, // 路由模式 // history(完全仿照浏览器历史记录模式) // 以后项目上线的话必须给服务器添加配置文件,支持 history 模式 // hash(锚点模式) mode: "history", // 将所有的 router-link 默认链接激活的类名修改成了 active,一般不用 // linkActiveClass: "active", }); // 3). 导出使用,在main.js 导入,添加到整个 vue 项目中 export default router; -
main.js
import Vue from "vue"; import App from "./App.vue"; // 路由的使用 虽然我们使用了 Vue.use 方法将 VueRouter 制作成了全局功能, // 但是想要使用我们创建好的路由还需要将创建好的路由加载到整个 vue 项目中 // 导入 js 文件的时候如果名字叫 index.js 可以省略直接写路径就行 // import router from "./router/index"; import router from "./router"; Vue.config.productionTip = false; new Vue({ router, // 其实添加 router 属性,主要是跟路由相关组件匹配,还有将$route 和$router 挂载到Vue实例当作公共属性, // 也就是说所有 Vue 组件都会有这两个属性 render: (h) => h(App), }).$mount("#app"); -
App.vue
<template> <div id="app"> <!-- 见下面的 router-view 和 router-link --> <div class="nav"> <router-link :to="{path:'/'}">首页</router-link>| <router-link :to="{name:'pins',query:{userId:123}}">沸点</router-link>| <router-link to="/books">小册</router-link> </div> <router-view /> </div> </template> <script> export default { name: "App", }; </script> <style> .router-link-exact-active { color: red; } </style> -
Home.vue,Books.vue,Pins.vue
<template> <div> <h2>我是Home</h2> <!-- Books和Pins只是这里的英文不一样,别的都一样 --> </div> </template> <script> export default { name: "Pins", }; </script>
# router-view
用来展示页面组件的 , 页面地址和某个路由对象的 path 匹配的话就会展示对应的页面组件
匹配的规则是在 routes 中 从上到下严格完全匹配(不包含查询部分?和锚点部分#),只能匹配一个
# router-link
(https://router.vuejs.org/zh/api/#router-link)
router-link 用来实现路由的跳转,也就是页面跳转
router-link 必须传递 to prop,值是跳转的地址,当地址和 to 匹配的时候,默认会添加一些激活的类名
匹配的规则是包含匹配,比如当地址是 /pins 的时候,匹配 /pins 和 /
to 属性的属性值也可以是一个对象 {path,name,params,query}
path:路径 name:命名路由的名字
query 是地址栏的查询部分,也就是 ? 部分
params 动态路由参数,只有路由的 path 设置了动态参数的时候才可以传递
to:表示目标路由的链接,这个值可以是一个字符串或者是描述目标位置的对象。replace:当点击时导航不会留下 history 记录。append:设置 append 属性后,则在当前 (相对) 路径前添加基路径。
例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/btag: 有时候想要渲染成某种标签 exact:精确匹配模式,比如当地址是 /pins 的时候,只匹配 /pins, 不再匹配 /active-class:设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。(.router-link-exact-active)exact-active-class:配置当链接被精确匹配的时候应该激活的 class。注意默认值也是可以通过路由构造函数选项 linkExactActiveClass 进行全局配置的。
<router-link :to="{name:'pins',query:{userId:123}}" append>沸点</router-link>|
<router-link :to="{path:'/'}">首页</router-link>|
<router-link :to="{name:'pins',params:{type:'hot'}}">沸点</router-link>|
<router-link to="/books">小册</router-link>
# router-link 如何通过 query 传参
-
App.vue
<template> <div id="app"> <div class="nav"> <router-link :to="{name:'pins',query:{type:'hot'}}">沸点</router-link>| </div> <router-view /> </div> </template> <script> export default { name: "App", }; </script> -
Pins.vue
这里是使用 query 来展示的
<template> <div> <h2>我是Pins</h2> <h3>现处于 {{type}} 类别页面</h3> </div> </template> <script> export default { name: "Pins", // 我是路由组件,路由跳转的时候传递了一些参数或者查询条件,需要根据这些条件,去展示不同的内容 // 传递方式有两种 // 1.query 是地址栏的查询部分,也就是 ?部分 // 2.params 动态路由参数,只有路由的 path 设置了动态参数的时候才可以传递 // 在路由组件内 如何获取当前路由的相关信息 // 只要是路由组件,那么该组件就会得到一个当前路由信息,存储在 this.$route 内 data() { return { type: "", }; }, created() { // console.log(this.$route); this.type = this.$route.query.type; }, }; </script>
# 动态路由
一个页面渲染不同的内容 (不同的路径要展示到相同的组件叫动态路由)
-
App.vue
<template> <div id="app"> <div class="nav"> <router-link :to="{name:'pins',params:{type:'hot'}}">沸点</router-link>| </div> <router-view /> </div> </template> <script> export default { name: "App", }; </script> -
Pins.vue
<template> <div> <h2>我是Pins</h2> <h3>现处于 {{type}} 类别页面</h3> </div> </template> <script> export default { name: "Pins", data() { return { type: "", }; }, created() { this.type = this.$route.params.type; }, }; </script> -
src 文件夹下自创的全局 router.js
import Vue from "vue"; import VueRouter from "vue-router"; import Pins from "./components/Pins.vue"; Vue.use(VueRouter); // 将所有的路由组件制作成了全局组件 const routes = [ // 动态路由 就是多个地址都展示一个页面,就需要设置动态路由参数 { // :type 就是地址栏内的变量(路由参数 路由参数可以有多个) 一定要带冒号才是变量 // 如果是 /pins 那么页面出不来;必须是 /pins/变量 页面才可以出来 path: "/pins/:type", // 表示 /pins/任何 都会匹配 component: Pins, name: "pins", }, ]; // 2). 根据路由数组创建 整体的路由 const router = new VueRouter({ routes, mode: "history", }); export default router;
# router 实例
$route 取得是当前路由 (页面) 的信息 $router 是整体路由
$router 是路由添加给所有组件的属性,可以直接访问 (注意模板语法),这个属性指的是整个路由实例,路由实例下有许多方法
push("地址") 实现路由的跳转,当你在当前页使用 push 方法跳转到当前页会有警告,是不允许重复导航,所以需要提前判断一下back()(什么也不用加) 实现历史记录返回go(数字)实现历史记录的跳转,前进后退都可以go(-1)就是 backreplace('地址')替换,和 push 一样,但是跳转没有历史记录forward()往前走,跟 back 相反的
-
logo 图跳转首页小案例
<div class="logo"> <!-- 点击logo图片跳转首页,使用 <router-link>包裹就能实现 --> <!-- 还可以直接绑定点击事件内执行 $router.push('/') --> <img @click="goHome" src="https://sf3-scmcdn2-tos.pstatp.com/xitu_juejin_web/img/logo.a7995ad.svg" alt /> </div> <script> export default { name: "App", components: {}, methods: { goHome() { // 当你在当前页使用 push 方法跳转到当前页会有警告,是不允许重复导航 this.$route.path !== "/" && this.$router.push("/"); }, }, }; </script>
# 子页面 (嵌套路由)
-
嵌套动态子路由案例
-
router.js
const routes = [ { path: "/pins/:type", component: Pins, name: "pins", }, { path: "/books", component: Books, name: "books", }, // 这里因为books和下面children的path重了,所以让books先匹配,后匹配 / 的孩子 // 动态路由和一些默认路由冲突的话,要把动态路由放到后边匹配 { path: "/", component: Home, // name: "home", // 命名路由 // 嵌套路由 // 当父路由匹配的时候,默认某一个子路由也需要匹配 children: [ { // 当嵌套的子路由地址是空的时候,父路由匹配的时候,这个会默认展示 // 当给了 父路由设置了 name 的时候,如果有默认子路由,请把 name 给默认子路由设置 path: "", component: List, name: "home", // 命名路由 }, { // 嵌套路由的 path 前面不要加 /,或者说前面不要加父路由path,默认会自动添加 path: ":category", component: List, }, ], }, ]; -
Home.vue 父组件
<template> <div> <h2>我是Home</h2> <div class="home-head"> <router-link to="/recommended">推荐</router-link> <router-link to="/frontend">前端</router-link> <router-link to="/backend">后端</router-link> </div> <!-- 这里要展示页面,因为是不同的路径要展示到一个页面,所以使用动态路由 --> <!-- 嵌套路由的展示就是直接在 父路由 组件内使用 router-view 即可 --> <router-view /> </div> </template> -
List.vue 子组件
<template> <div>我是{{$route.params.category || 'recommended'}}文章列表组件</div> </template> <script> export default { name: "List", }; </script>
-
# 重定向和别名跳转页面
-
案例 router.js
const routes = [ { // /user/:username/post/:post_id /user/evan/post/123 {username:'evan',post_id:"123"} // 路由参数会被设置到 this.$route.params 中, 它负责动态路由的参数 path: "/pin/:type", component: Pins, alias: "/pins/:type", // 别名方式 name: "pins", }, // 网站改版了,沸点页的网址不能是 pins 了,需要是 pin,但是偶尔也会有一些用户输入 pins 网址,我们也希望可以访问沸点页 // 1. 重定向实现 pins/hot ---> pin/hot ,用户看不到pins,改成了pin // 2. 别名,用户可以看到pins // { // path: "/pins/:type", // redirect: "/pin/:type", // redirect: { name: "pins" }, // 重定向实现 // }, ];
# 路由组件传参
-
router.js 文件
import Vue from "vue"; import VueRouter from "vue-router"; import Home from "./components/Home.vue"; import List from "./components/List"; import NotFound from "./components/NotFound"; Vue.use(VueRouter); const routes = [ { path: "/", // 命名视图,不是命名路由 components: { default: Home, error: NotFound, }, children: [ { path: "", component: List, name: "home", // 命名路由 // 当这个路由被匹配的时候是没有动态路由参数的 其实组件内获取的是 undefined,我们想让组件获取 recommended // 直接将 props 写成对象类型即可,props 要写成静态的 // 2.对象类型 props: { category: "recommended", }, }, { path: ":category", component: List, // 当路由对象给props属性设置了true,那么 $route.params 将会被设置为组件属性 // 也就是说params对象下的属性会当作prop传递给组件 // 所以我们可以使用 props 替代 $route 在组件内获取动态路由参数 // 这里加 true 是给 List 组件添加 prop,会把 $route.params(动态路由参数) 当作prop // 在哪里加 true 就会给哪个组件添加prop // 1. 布尔值类型 // props: true, // 3.可以使用 函数代替 true,函数默认接收当前的路由为参数 props: (route) => ({ category: route.params.category, }), }, ], }, // NotFound 页面 { path: "*", component: NotFound, }, ]; -
List.vue 文件
<template> <div> <!-- 这里的 category 是 prop --> <h3>我是{{category || 'recommended'}}文章列表组件</h3> <div> <!-- 展示列表内容 --> <div v-for="item in list" :key="item.id">{{item.title}}</div> </div> </div> </template> <script> export default { name: "List", data() { return { list: [], isNotFound: false, }; }, props: ["category"], // 这里接收的 prop 是 router.js 中动态路由的参数名字 watch: { // 通过监听路由参数的变化,从而更新数据 // "$route.params.category": { // 这个 category 是 prop category: { handler(newValue) { // newValue 取的是 $route.params.category 的值 // 当动态路由参数并不是你的文章分类,请求肯定获取不到数据 setTimeout(() => { if (newValue === "recommended") { // 要避免 / <----> /recommended 产生重复的请求 // 1. 就需要 newValue, oldValue 进行判断 // 2. 可以直接给组件设置默认的 category prop console.log(111111111, "请求推荐类的文章"); this.list = [{ id: 1, title: "推荐类文章1" }]; } else if (newValue === "frontend") { this.list = [{ id: 1, title: "前端类文章1" }]; } else if (newValue === "backend") { this.list = [{ id: 1, title: "后端类文章1" }]; } }, 50); }, immediate: false, }, }, }; </script>
# 路由导航守卫
-
路由全局前置导航守卫
-
Login.vue
<template> <div class="login"> 用户名: <input type="text" v-model.lazy.trim="username" /> <br />密码: <input type="text" v-model.lazy.trim="password" /> <br /> <button @click="login">登录</button> </div> </template> <script> export default { name: "Login", data() { return { username: "", password: "", }; }, methods: { login() { if (this.username === "admin" && this.password === "123456") { // 浏览器的本地存储 localStorage 永久存 sessionStorage 页面关了就存不了了,只能存数值,布尔值,字符串 sessionStorage.setItem("isLogin", true); this.$router.push("/"); } }, }, }; </script> -
index.js
import Vue from "vue"; import VueRouter from "vue-router"; import Home from "../views/Home.vue"; Vue.use(VueRouter); const routes = [ { path: "/", name: "Home", component: Home, }, { path: "/about", name: "About", // 路由懒加载,当路由不匹配到 About 页面的时候,About 组件不会导入 component: () => import("../views/About.vue"), }, { path: "/login", name: "Login", component: () => import("../views/Login.vue"), }, ]; const router = new VueRouter({ mode: "history", routes, }); // 查看登录状态,我们的想法是在每个组件的 created 中获取登录状态 // 更好的方法是路由全局导航守卫 // router.beforeEach 全局前置守卫,路由跳转就触发,还没有跳转过去,可以控制跳不跳转,必须设置next(路由地址) // to 目标路由地址信息 // from 是来源路由地址信息 router.beforeEach((to, from, next) => { // 获取登录状态,控制路由跳转 const isLogin = Boolean(sessionStorage.getItem("isLogin")); if (to.path !== "/login" && !isLogin) next("/login"); next(); }); export default router;
-
# 路由元信息
-
index.js 文件
import Vue from "vue"; import VueRouter from "vue-router"; import Home from "../views/Home.vue"; Vue.use(VueRouter); const routes = [ { path: "/", component: Home, children: [ { path: "", name: "Home", component: () => import("../views/Index.vue"), }, { path: "about", name: "About", component: () => import("../views/About.vue"), // 路由元信息 meta: { requireAuth: true }, }, ], }, ]; const router = new VueRouter({ mode: "history", routes, }); export default router; -
About.vue 文件
<template> <div class="about"> <h1>This is an about page</h1> </div> </template> <script> export default { created() { console.log(this.$route); // 匹配的路由 $route 下有一个 matched 属性,该属性是一个数组,记录了当前地址匹配到的路由记录 // 路由记录可以直接使用 $route 访问到当前路由 // 在路由记录内可以访问到对应的路由的 路由元信息(meta字段) // 通过路由元信息,添加权限属性,可以实现权限处理问题 // 实现的就是当前地址会匹配到很多路由(包括嵌套的子路由),只要某一个路由通过路由元信息设置了权限, // 那么就需要遍历 $route.matched 去查询 // 在这的判断最好去路由的全局前置守卫去判断,在这只是做个测试 if (this.$route.matched.some((item) => item.meta.requireAuth)) { // 说明需要权限 需要判断当前是否有权限,没有的话跳转到首页 const auth = "普通用户"; // 去不了about页面,值为超级管理员就可以去 if (auth !== "超级管理员") { this.$router.push("/login"); } } }, }; </script>
# 滚动行为
切换路由时的滚动行为,是路由里边的
-
index.js
const router = new VueRouter({ mode: "history", routes, // 滚动行为 scrollBehavior(to, from, savedPosition) { // 该函数的返回值就是滚动条的位置 // 控制的是 window 的滚动条 // savedPosition 当且仅当(通过浏览器的 前进/后退 按钮触发) 时才可用。 // (1.浏览器左上角的返回按键 2.history 的 back 或 go),保留原来页面的滚动条位置 if (savedPosition) { return { ...savedPosition, behavior: "smooth" }; // behavior: "smooth" 平滑滚动 } else { return { x: 0, y: 0 }; } }, }); -
List.vue
<template> <div class="list"> <button class="back" @click="back">返回</button> </div> </template> <script> export default { name: "List", methods: { back() { // 返回上一页 this.$router.back(); }, }, }; </script> <style scoped> .list { height: 2000px; } .back { position: fixed; bottom: 50px; right: 50px; } </style>