可算玩明白路由了--vue-router4
前言
vue-router
是什么,为什么需要vue-router
,在vue
全家桶中vue-router
又扮演着什么样的角色呢?我们都知道vue
主要构建的是单页面应用,所有的数据显示都在一个页面中进行,这样大大的提高了首屏加载的速度,怎么在一个页面中显示所有的数据呢?这个时候vue-router
表现的时候就到了,经过路径地址的切换,让同一个页面中显示不同的内容就是vue-router
主要功能,不仅如此还有这一下的几个特点!
- 嵌套路由:允许你创建子路由,这些子路由会继承父路由的 path。
- 模块化的、基于组件的路由配置:将路由配置和组件定义在同一个文件中,使得代码更加清晰和易于维护。
- 路由参数:允许你定义动态路由,这些路由可以根据 URL 中的参数变化。
- 查询参数:支持 URL 查询参数。
- 导航守卫:允许你在路由改变之前进行一些操作,例如权限检查。
- HTML5 历史模式:支持 HTML5 的 history API,允许你使用更人性化的 URL。
- 过渡动画:支持 Vue.js 的过渡动画。
- 支持服务端渲染。
正文
安装与注册router
//可以选择指定版本,也可以选择安装最新版
npm install vue-router@latest //安装最新版本
//或
yarn add vue-router@4 //使用yarn安装v4最新版本
安装好了以后就可以注册路由,并把路由挂载到vue上了
在注册路由的时候,可以选择想使用的路由模式分别为:has
模式和history
模式
has
模式的路由在导航栏中会有一个#最为识别的符号,看上去不那么美观,并且它在 SEO 中确实有不好的影响
history
模式则是H5中新增的一种模式,取消了标识符#,使导航栏看着比较美观,但是在这种情况下部署的时候需要后端稍微配置一下,否则会出现404的情况,并且对SEO也更友好
//nginx配置
location / { try_files $uri $uri/ /index.html; }
//官方也有给出其他配置:https://router.vuejs.org/zh/guide/essentials/history-mode.html
注册路由:
//单独创建一个名为router的文件夹,并创建文件,在这里注册路由
import { createRouter, createWebHashHistory,createWebHistory } from "vue-router";
import routes from "./routes";
//从vue-router中引入createRouter并创建路由实例
const router = createRouter({
//这里是路由模式
//createWebHashHistory为has模式,createWebHistory为history模式
history: createWebHashHistory(),
//路由的配置选项,routes为单独引进的
routes,
});
export default router;
//routes.js
export default [
{
path: "/",
name: "Home",
meta: { title: "首页", keepAlive: true },
component: () => import("../views/Home.vue"),
children: [],
}
]
将路由挂载到vue上
//main.js
import { createApp } from "vue";
import App from "./App.vue";
//刚才默认导出的router实例
import router from "./router";
//使用vue.use()方法就可以了
createApp(App).use(router).mount("#app");
routes各个属性的作用
刚才注册路由的时候,就看到routes是一个数组包对象的格式,对象中有许多属性,现在来说明一下各个属性的有什么作用,在使用中是否能够经常用到
path
:指定地址,有多种指定方式,也可以使用正则表达式来指定,例如path:'/',path:'/:orderId(\\d+)'
name
:命名路由的name,当有一个存在name属性的时候,尽量所有的路由都添加name,name必须是唯一的,跳转的时候可以和path一样指定跳转meta
:元数据。为一个对象,里面存放一些自己可能用的到数据,使用route
可以获取到这里面的数据component
:指定组件。可以为一个组件,也可以为一个对象,在路由懒加载的时候为一个方法。当为一个组件的时候,地址栏中输入指定的path
,页面中就会显示component
对应的组件。当为一个对象的时候components
(加s)对象名为显示的名字,对象值为组件,可以针对不同的名字显示在不同的位置,也被称为命名视图children
:嵌套路由。为一个数组,里面数据结构与当前结构是一致的redirect
:重定向。结构与path
一致,当地址为这个对象里的path
时,会自动改成redirect
的路径进行跳转(不要重定向自己,栈溢出的),也可以是一个方法,参数是目标路由,必须要有返回值,返回值可以是个对象,也可以是个字符串alias
:路由别名。是个字符串,为一个别名,也可以是个数组,定义多个别名props
:路由组件传参。布尔值,当为true的时候,route.params
的值将作为props
传递给路由组件,也可以是个对象,属性名为命名视图的名字,属性值为布尔值,跟单写一个布尔值效果一致,也可以是一个方法,参数为route
,必须要有返回值,返回值为传递的参数,建议为对象形式
//routes.js
export default [
{
//路径地址
path: "/",
//添加了name属性就是命名路由,名字是唯一的,跳转的时候也可以使用name
name: "Home",
//元数据,可以自定义一些自己需要的数据
meta: { title: "首页", keepAlive: true },
//指定组件,当地址栏输入对应地址的时候,页面中会显示相对应的组件
component: () => import("../views/Home.vue"),
//嵌套路由,内部解构和外面整体是一样的
children: [],
//重定向 出本身以外要有一个路由叫/home的
redirect: "/home",
//别名,现在地址栏中输入这个也可以条状到对应的路由
alias: "/h",
//路由组件传参
props: true
}
]
vue-router内置组件
router-view
用于路由组件显示的位置
router-link
用于路由跳转
//routes.js
//这里是定义了三个路由页面
export default [
{
path: "/",
name: "Home",
component: () => import("../views/Home.vue"),
},
{
path: "/detail",
name: "Detail",
component: () => import("../views/Detail.vue"),
},
{
path: "/list",
name: "List",
component: () => import("../views/List.vue"),
}
]
//app.vue
//router-link 有一个必传属性to 属性值为path的地址
<router-link to="/">首页</router-link>
<router-link to="/detail">详情页</router-link>
<router-link to="/list">列表页</router-link>
//这里就是显示路由页面的地方
<router-view />
router-link
在html中会渲染成a标签,当点击他们的时候就router-view
的位置就会显示path
相对应的component
当然
router-link
的跳转方式不止这一种,也可以是对象形式
//app.vue
//这种跳转方式 和单独写path路径是一样的
<router-link :to="{ path: '/' }">首页</router-link>
<router-link :to="{ path: '/detail' }">详情页</router-link>
<router-link :to="{ path: '/list' }">列表页</router-link>
如果routes
中有写name
属性,也可以使用命名路由的name
名进行跳转,之前说过name
是必须是唯一的,所以也不怕名字会冲突造成一些不可预知的问题
//app.vue
<router-link :to="{ name: 'Home' }">首页</router-link>
<router-link :to="{ name: 'Detail' }">详情页</router-link>
<router-link :to="{ name: 'List' }">列表页</router-link>
router-link
还有另外一个属性 replace
,是否替换当前路由。在浏览器左上角可以看到是有一个后退按钮的,在正常情况下点击条状路由之后是可以点击后退按钮进行回退操作的,但有些情况下我不想让用户回退,比如登录页登录之后,这时候就可以添加replace
属性,替换当前路由页面,禁止回退
//app.js
//当点击首页之后就不能够进行回退操作
<router-link :to="{ name: 'Home' }" replace>首页</router-link>
//点击这两个是可以正常进行回退操作的
<router-link :to="{ name: 'Detail' }">详情页</router-link>
<router-link :to="{ name: 'List' }">列表页</router-link>
编程式导航
编程式导航就是不使用router
提供的内置组件跳转,而是在js中进行式跳转
router
实例上有一个push
方法,效果是和router-link
的to
属性一样,使用方法和to
的使用方法也是一致的
//app.vue
//以下采用的是v3中的写法
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const to = () => {
//三种方法效果都是一样的
router.push("/");
router.push({ path: "/" });
router.push({ name: "Home" });
};
</script>
也是可以替换路由,禁止后退的,当push
方法的参数为一个对象的时候可以添加 {reolace:true}
,来实现路由替换,在router
实例上也有一个replace
的方法,调用此方法就是替换路由了,写法与push
一致
//app.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const to = () => {
//这两种写法效果是一样的
router.push({ path: "/", replace: true });
router.replace("/");
};
</script>
其实没有多少人喜欢点击浏览器自带的后退按钮,router
实例上也提供back
和go
两个方法,back
方法只是单纯进行回退操作,go
方法可以是回退、前进和刷新页面
//app.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
const back = () => {
//back 回退
router.back();
//go方法 接收number参数
//负数为后退 number是几 就是后退几次(几步)
router.go(-1);
//number为0 就是刷新当前页面
router.go(0);
//正数就是前进,number为几就是前进几次(几步),当然必须要有前进的页面
router.go(1);
};
</script>
路由传参和动态路由
由于路由传参需要动态路由,所以就将这两个看似不相干的写到一起了
先说动态路由,前面说过path路径的指定方式有很多,不仅仅只能是固定的路径,当在路径前面加上:
的时候,这个路径的名字就可以使任意的了,例如:{path:'/:id'}
,跳转到这个路径只需要一个/
加任意一个参数或者什么都不加
//routes.js
export default [
{
path: "/",
name: "Home",
component: () => import("../views/Home.vue"),
},
//这里detail路由为一个动态路由
{
path: "/detail/:id",
name: "Detail",
component: () => import("../views/Detail.vue"),
},
{
path: "/list",
name: "List",
component: () => import("../views/List.vue"),
}
]
//app.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
let r = Math.random();
const to = () => {
//在跳转的defail的时候,需要添加动态路由,这里必须要是两个/的跳转
router.push(`/detail/${r}`);
};
</script>
单看动态路由感觉并没有什么用,这里就说到路由传值
跳转页面的时候有些情况下需要将这个页面的一些参数传递到要跳转的页面,要跳转的页面可以接收到参数来进行数据请求,这里route
实例来进行接收参数行为
//detail.vue
<script setup lang="ts">
import {useRoute} from 'vue-router'
const route = useRoute()
//在detail组件内定义route,并打印route的实例看看
console.log(route);
</script>
可以看出
route.params.id
就是动态路由上的值,id
是定义动态路由的时候,:
号后的的名字,拿到这个参数就可以进行请求发送获取数据等一系列操作了(这里可以看出route实例上是有meta元数据的,在定义的时候写号meta在访问route实例的时候就可以直接使用。)
路由传参总共有两种(router4.x,4.x之前是有三种的)
动态路由这种输入params
传参,使用?拼接的为query
传参
//app.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
let r = Math.random();
//一下是多种方式传递进行路由传参
const to = () => {
// params传参 接受的时候是在route.params中
router.push(`/detail/${r}`);
//使用这种写法,在地址栏中也会解析成第一种
router.push({
name: "Detail",
params: {
id: r,
},
} );
// qeury传参 接受的时候是在route.query中
router.push(`/detail?id=${r}`);
//使用这种写法,在地址栏中也会解析成第一种
router.push({
path: "/detail",
query: {
id: r,
},
});
};
</script>
这是params
传参的地址栏和参数,需要加上动态路由,否则会有警告提示
//params传参的路由定义
{
path: "/detail/:id",
name: "Detail",
component: () => import("../views/Detail.vue"),
}
这是
query
传参的地址栏和参数,只需要要跳转的路由地址即可
//query传参的路由定义
{
path: "/detail",
name: "Detail",
component: () => import("../views/Detail.vue"),
}
上面可以看出
query
传参的话所定义的字段和值都会被看到一清二楚,而params
传参跟正常路由无异
第三种传参是定义路由的时候不定义动态路由且使用params传参,这种在刷新页面的时候数据会丢失,所以不建议使用,当然在v4.x中也已经不能这么写了
扩展
动态路由可以使用正则表达式或一些符号来限制路由传递的参数
//routes.js
//...
//有且只能必须有一个动态路由
path: "/detail/:id",
//动态路由只能匹配数字
path: "/detail/:id(\\d+)",
//至少有一个动态路由 例如:/:id -->/123,/213/324,/24/2352/532 等
path: "/detail/:id+",
//可以有任意个由 例如:/:id -->/,/123,/213/324,/24/2352/532 等
path: "/detail/:id*",
//有0-1个路由 例如:/detail/:id-->/detail,/detail/213
path: "/detail/:id?",
//...
有时候需要捕获所有路由路径,如果捕获到的路由没有对应的组件,就需要跳转到404页面,捕获所有路由一定要放到最后,当全部的路由都找不到才能匹配它,如果将它放在前面,后面有的路由路径就匹配不到了
//routes.js
{
//使用全局匹配,来达到捕获所有路由
path: "/not-found(.*)*",
name: "/NotFound",
component: ()=>import('../views/NotFound.vue'),
},
路由守卫
beforeEach
前置守卫,在路由跳转之前进行一些操作,接收三个参数,第一个to
,要去往的路由参数,第二个参数from
,从哪来的的路由参数,第三个参数next
,可选参数,是一个方法,是否通过或者跳转到某个路由页面,在v4.x中并不推荐next跳转,推荐return写法(下面示例使用的return)
const router = createRouter({
history: createWebHashHistory(),
routes,
} );
//实例上的方法,支持异步
router.beforeEach(async ( to, from,next ) => {
//...
//当前页面在首页并且要跳转到登录页 就禁止跳转
if(to.path == '/'&&from.path == 'login') return false;
//在登录页 异步请求,获得数据如果有,就跳转到首页
if (to.path == "/login") {
const token = await fetch("http://xxx.com").then((res) => res.json());
if (token.data) {
//return 出的path路径 就是要跳转的路径
return "/";
}
}
//如果身上有params的id参数那就跳转到详情页
if (from.params.id) {
//return 支持对象传输,跟push跳转页面的参数一致
return { name: "Detail" };
}
//当retrun 为真时,跳转页面不收任何限制
return true
})
如果要使用next
跳转,跟将return
处替换成next
即可,例如:next(false)
禁止跳转,next('/')
跳转到首页,next({name:'Detail'})
跳转到详情页,next()
不做跳转限制
afterEach
后置守卫,接收两个参to
,from
和前置守卫一致,因为已经跳转页面了,所以不会再进行限制跳转
const router = createRouter({
history: createWebHashHistory(),
routes,
});
//实例上的方法
router.afterEach( ( to, from ) => {
//...进行一些操作
})
beforeEnter
路由独享守卫,接收三个参数,to,from,next,使用方法和beforeEach一致,只不过是某个路由独享的
//routes.js
export default [
{
path: "/",
name: "Home",
component: () => import("../views/Home.vue"),
//这这里定义路由守卫,只会对这个路由页面进行拦截
//在v4.x中依旧是推荐return方式
beforeEnter: ( to, from, next ) => {
//...进行一些操作,判断是否跳转
return true
},
}
]
组件内路由守卫,使用和操作与beforeEach一致,同样是拥有三个参数,to
、from
和next
beforeRouteEnter
组件在渲染之前,由于是在渲染之前,所以在这个方法内是访问不到vue
的实例的,也就是拿不到data里的数据,如果需要在这里访问data
中的数据,next
方法接收一个回调函数,回调函数里的参数就是vue
实例(setup写法中不提供此方法)
beforeRouteUpdate
组件更新时,在这里next方法不再接收回调函数,因为没必要
beforeRouteLeave
组件离开前,这里同理,next不再接收回调函数作为参数
<script>
export default {
name: "Home",
data() {
return {};
},
beforeRouteEnter( to, from, next ) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate( to, from, next ) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave( to, from, next ) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
};
</script>
//setup中
<script setup lang="ts">
import { onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";
onBeforeRouteUpdate((to, from, next) => {
//...
});
onBeforeRouteLeave((to, from, next) => {
//...
});
</script>
滚动行为
有时候路由跳转了之后,希望跳转到某一个路由页面的某一个位置,就可以在定义router实例的时候来定义滚动行为这个功能只在支持 history.pushState 的浏览器中可用
创建实例的时候,会有一个scrollBehavior
方法,接收三个参数to、from和savedPosition,to和from都知道了,savedPosition
是只有当这是一个 popstate
导航时才可用(由浏览器的后退/前进按钮触发)。该函数可以返回一个 ScrollToOptions
位置对象
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior( to, from, savedPosition ) {
//return期望滚动的位置
return {top:0}//每次跳转都滚动到顶部
}
});
//你也可以通过 `el` 传递一个 CSS 选择器或一个 DOM 元素。
//在这种情况下,`top` 和 `left` 将被视为该元素的相对偏移量
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior( to, from, savedPosition ) {
//相对于id是mine的元素top值为100的位置
return {
el:'#mine'
top:100
}
}
});
//当点击浏览器的前进后退时,也可以使用savedPosition返回的位置,另外如果浏览器支持也可以优化滚动效果
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior( to, from, savedPosition ) {
//如果它有值,则会返回此值的位置,否者回到最顶部
if (savedPosition) {
return savedPosition;
} else {
return {
el: to.hash, //定义路由的时候,所写的hash值,为一个id标识
behavior: 'smooth',//优化滚动效果属性
};
}
}
});
//如果不想在跳转路由的时候立马滚动,也可以返回一个promise,来达到异步滚动的效果
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
return new Promise( resolve => {
setTimeout(() => {
resolve({ left: 0, top: 0 });
} )
})
},
});
过渡动效
切换路由的时候如果感觉太过生硬了,可以利用vue
提供的内置组件Transition
来实现每次跳转页面的时候都有动效,看着没有那么生硬,所以在这需要会使用Transition
组件的使用
//app.vue
<router-view v-slot="{ Component }">
<Transition name='hide'>
<component :is="Component" />
</Transition>
</router-view>
也可以使用元数据,对每个路由页面进行不同的动效处理
//routes.js
export default [
{
path: "/",
name: "Home",
component: () => import("../views/Home.vue"),
meta: { transiton: "left" },
},
{
path: "/detail/:id+",
name: "Detail",
component: () => import("../views/Detail.vue"),
meta: { transiton: "right" },
},
{
path: "/list",
name: "List",
component: () => import("../views/List.vue"),
meta: { transiton: "top" },
}
]
//app.vue
<router-view v-slot="{ Component, route }">
//获取元数据中的内容,如果有就走元数据,没有就默认动效
<Transition :name="route.meta.transition || 'hide'">
<component :is="Component" />
</Transition>
</router-view>
结尾
vue-router
作为官方提供路由管理库,与vue的契合性非常好,也提供了比较简便的使用方法,并且在一直维护更新中,相比较之前的版本v4.x版本中对许多功能进行了优化,包括但不限于
- 模块化:Vue Router 4进行了项目结构优化,将其分为三个模块,这使得Vue Router更加模块化、灵活,并提供了更好的扩展性和可维护性。
- 动态路由:Vue Router 4的动态路由功能为路由提供了更大的灵活性和功能性。通过高级路径解析功能,实现了自动优先级排名路由。
- 更好的导航状态:Vue Router 4可以提供更强大的 Devtools,它能够和浏览器进行更高级的整合,如时间轴记录路由变化,帮助用户轻松进行调试。
转载自:https://juejin.cn/post/7282692354984034315