Vue2路由 VueRouter
2023-08-12 本文已影响0人
h2coder
Vue路由,也就是VueRouter,Vue2和Vue3有版本之分,本文以Vue2为例
- Vue2,使用3.x版本
- Vue3,使用4.x版本
添加依赖
npm install vue-router@3.6.5
基础使用
- 一般会将路由的配置,单独抽到
router
目录下的index.js
文件,统一管理路由路径 - 下面,以3个路由页面为例,通过点击不同的a标签,进行页面切换,分别是:
- Find.vue:发现页面
- Friend.vue,我的好友页面
- My.vue,我的页面
创建VueRouter实例,并配置地址和组件的映射关系
- router\index.js
import Vue from 'vue'
// 引入VueRouter
import VueRouter from 'vue-router'
// 路由页面组件
import Find from "../views/Find.vue";
import My from "../views/My.vue";
import Friend from "../views/Friend.vue";
// 安装注册
Vue.use(VueRouter);
// 创建路由实例,传入配置对象,配置url和组件的映射关系
const router = new VueRouter({
routes: [
// 发现音乐
{
// #号后面的url地址
path: '/find',
// url对应的组件
// 注意:属性名是component,不是components,是不带s的
component: Find
},
// 我的音乐
{
path: '/my',
component: My
},
// 朋友
{
path: '/friend',
component: Friend
},
]
});
// 默认导出,导出路由实例
export default router;
注册路由给Vue实例
import Vue from 'vue'
import App from './App.vue'
// 导入路由实例
import router from './router/index.js'
Vue.config.productionTip = false
// 注入路由对象给Vue实例
new Vue({
render: h => h(App),
router
}).$mount('#app')
首页(App.vue)
- 提供3个a标签,用于跳转页面
- 路由视图标签,也就是不同路由界面组件的内容,会渲染到
<router-view></router-view>
中
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/friend">朋友</a>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style>
body {
margin: 0;
padding: 0;
}
.footer_wrap {
position: relative;
left: 0;
top: 0;
display: flex;
width: 100%;
text-align: center;
background-color: #333;
color: #ccc;
}
.footer_wrap a {
flex: 1;
text-decoration: none;
padding: 20px 0;
line-height: 20px;
background-color: #333;
color: #ccc;
border: 1px solid black;
}
.footer_wrap a:hover {
background-color: #555;
}
</style>
准备3个路由页面
- Find.vue,发现页面
<template>
<div>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
<p>发现音乐</p>
</div>
</template>
<script>
export default {
name: 'FindMusic'
}
</script>
<style>
</style>
- Friend.vue,我的好友页面
<template>
<div>
<p>我的朋友</p>
<p>我的朋友</p>
<p>我的朋友</p>
<p>我的朋友</p>
</div>
</template>
<script>
export default {
name: 'MyFriend'
}
</script>
<style>
</style>
- My.vue,我的页面
<template>
<div>
<p>我的音乐</p>
<p>我的音乐</p>
<p>我的音乐</p>
<p>我的音乐</p>
</div>
</template>
<script>
export default {
name: 'MyMusic'
}
</script>
<style>
</style>
导航链接高亮
-
VueRouter在切换路由页面时,会给当前选中的导航标签,选中方式给是给
a标签
设置类名,所以我们只需要给这些类名添加样式,就能实现选中效果 -
VueRouter会在路由跳转时,进行路径匹配,分为精准匹配和模糊匹配
-
精确匹配:地址和路由的URL完全一致,精确匹配,如:http://localhost:8080/#/my
-
模糊匹配:地址在路由URL的基础上,再多加几个层级,如:http://localhost:8080/#/my/123、http://localhost:8080/#/my/123/abc
-
精确匹配有2个样式,而模糊匹配只有一个,多个类名目的是为了实现嵌套路由的高亮(嵌套路由的2个Tab都选中)
- 精确匹配:
.router-link-exact-active
、.router-link-active
- 模糊匹配:
.router-link-active
- 精确匹配:
-
如果使用
a标签
,我们需要将href
属性写成类似#/my
的格式,但我们注册给VueRouter的路径只有/my
,而前面的#/
只是使用hash路由,这样重复书写太繁琐,而且不方便切换为history
路由,所以VueRouter还提供了router-link
标签给我们 -
router-link
标签,在H5中,其实就是a标签,该标签使用to
属性指定路由路径,也就是相当于a标签的href
属性
<template>
<div>
<div class="footer_wrap">
<!-- 路由链接,是VueRouter封装的组件,本质就是一个a标签,通过to属性,来实现a标签的href属性 -->
<!-- 注意,to属性,不需要加#号 -->
<!-- 如果用a标签,href属性,还要加上#/,太麻烦 -->
<!-- router-link组件,还可以实现跨平台,底层能映射为小程序和原生app对应的跳转功能 -->
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
<style>
// ...
/*
exact:是确切的意思
精确匹配:会有2个类名,.router-link-exact-active、.router-link-active
模糊匹配:只有一个类名,.router-link-active
没匹配上,没有上面2个类名
疑问:为什么要有区别?
- 方便后面做路由嵌套,#/my/cart => 第一层是my,第二层是cart
模糊匹配
- #/my/a #/my/b #/my/c => 有效
- #/my123 #/my456 => 无效
*/
.footer_wrap .router-link-exact-active {
background-color: blue;
color: white;
}
</style>
自定义导航高亮类名
- 上面说到VueRouter会给命中的路由的
router-link
标签或a
标签,设置类名,来实现高亮。这2个类名都挺长的,其实这2个类名是可以自定义的
在路由实例上配置类名
import Find from '../views/Find'
import My from '../views/My'
import Friend from '../views/Friend'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
// routes 路由规则们
// route 一条路由规则 { path: 路径, component: 组件 }
routes: [
{ path: '/find', component: Find },
{ path: '/my', component: My },
{ path: '/friend', component: Friend },
],
// 自定义精确匹配的类名
linkExactActiveClass: "exact-active-link",
// 自定义模糊匹配的类名
linkActiveClass: "active-link",
})
export default router
设置类名对应的样式
//...
<style>
/* 精确匹配 */
.exact-active-link {
background-color: red;
color: white;
}
</style>
导航链接传参-查询参数传参
- 查询参数传参,和Ajax请求的查询参数类型,都是URL地址?号后面,添加参数1名=值1&参数名2=值2,这种方式适合参数比较多时,看参数名就能知道参数值对应哪个参数
- 准备2个页面,分别是:
- 首页,Home.vue
- 搜索结果页面,SearchResult.vue
路由配置
import Home from '../views/Home'
import Search from '../views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
// VueRouter插件初始化
Vue.use(VueRouter)
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
{ path: '/search', component: Search }
]
})
export default router
首页
- 通过
router-link
标签的to
属性,设置路径和参数
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text" />
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<!-- 查询参数传参 (比较适合传多个参数) -->
<!-- 路由(导航)链接,参数放在to属性中,通过URL传参的方式,也就是?号后面,参数名=参数值&参数名2=参数值2 -->
<router-link to="/search?words=程序员">程序员</router-link>
</div>
</div>
</template>
<script>
export default {
name: "HomePage",
};
</script>
<style>
// ...
</style>
搜索结果页面
- 通过Vue实例的
$route
路由对象,读取query
属性,就能前面页面传递过来的查询参数
<template>
<div class="search">
<p>搜索关键字: {{ $route.query.words }}</p>
<p>搜索结果:</p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: "MyFriend",
created() {
console.log(this.$route);
// 获取首页传过来的参数,格式:$route.query.参数名
console.log(this.$route.query.words);
},
};
</script>
<style>
// ...
</style>
导航链接传参-动态路由传参
- 处理使用查询参数来传递页面参数外,还可以使用URL路径传参的方式,这种传参方式在Ajax请求中也是有的,在VueRouter中,被成为动态路由
- 参数名,并不是在导航标签上配置,而是在路由配置中
- 这种方式适合参数只有少量的时候,例如1个。当然也可以多个,但没有参数名,并不直观,例如:
- 参数
- 搜素关键字:新闻
- 页码:1
- 一页15条
- 结果:/search/新闻/1/15
配置路由
import Home from '../views/Home'
import Search from '../views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
// VueRouter插件初始化
Vue.use(VueRouter)
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home
},
{
// 动态路由传参,格式:/路径/:参数名
// 注意:如果没有配置,或配置出错,那就页面跳转的路由匹配就不正确,会白屏
path: '/search/:words',
component: Search
}
]
})
export default router
首页
- 通过
router-link
标签的to
属性,配置路径,注意参数是直接通过路径,没有查询参数
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text" />
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<!-- 动态路由传参 (优雅简洁,传单个参数比较方便) -->
<!-- 导航链接,动态路由传参,格式:/路径/参数 -->
<router-link to="/search/程序员">程序员</router-link>
</div>
</div>
</template>
<script>
export default {
name: "HomePage",
};
</script>
<style>
// ...
</style>
搜索结果页
- 通过Vue实例的
$route
属性,获取到路由对象,通过它的params
属性,就可以访问到前面页面传递的参数
<template>
<div class="search">
<!-- 获取动态路由传参 -->
<p>搜索关键字: {{ $route.params.words }}</p>
<p>搜索结果:</p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>
<script>
export default {
name: "SearchResult",
created() {
console.log(this.$route);
console.log(this.$route.params.words);
},
};
</script>
<style>
// ...
</style>
动态路由-可选符
- 上面说到,参数可以通过查询参数或路径传参(动态路由)的方式来传递,其中动态路由可以配置参数为可选,只需要在路由配置中的参数后面加一个
?
号可选符即可
路由配置
import Home from '../views/Home'
import Search from '../views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/home', component: Home },
// /search/:words ,表示必须要传参数
// 如果不传参,就会匹配不上,就不会显示 Search 组件
// { path: '/search/:words', component: Search },
// 如果不传参数,也希望匹配,那就加个可选符?号
{ path: '/search/:words?', component: Search }
]
})
export default router
首页
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text" />
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search">不传参</router-link>
<router-link to="/search/程序员">程序员</router-link>
</div>
</div>
</template>
<script>
export default {
name: "HomePage",
};
</script>
<style>
// ...
</style>
路由重定向
- 前面的路由配置,都是路径在
/home
或/search
时,才能路由正确,而还有一个页面根路径/
也是经常用的,但目前使用根路径跳转会一片空白,因为没有一个规则和它匹配。 - VueRouter还可以配置路由重定向,当路由到根路径时,重定向到
/home
首页 - 通过
redirect
属性,指定/
根页面时,则路由到/home
页面
import Home from '../views/Home'
import Search from '../views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
// VueRouter插件初始化
Vue.use(VueRouter)
// 创建了一个路由对象
const router = new VueRouter({
routes: [
// 配置路由重定向,如果访问的是根路径,则切换到 /home
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search }
]
})
export default router
路由404页面配置
- 除了路由重定向,VueRouter还能支持路由不到匹配的路径时,还能跳转到404页面,这在网站开发中是很常见的,如果将404页面样式美化得很好看,也能提升用户体验
- 通过
*
号通配符,VueRouter会在匹配完所有路径都没有匹配到时,跳转到配置了*
路径的组件页面
import Home from '../views/Home'
import Search from '../views/Search'
import NotFound from '../views/NotFound.vue'
import Vue from 'vue'
import VueRouter from 'vue-router'
// VueRouter插件初始化
Vue.use(VueRouter)
// 创建了一个路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/search/:words?', component: Search },
// 路由404,*表示通配符,匹配任意路径,当路径不匹配时,就命中这个规则,跳转到一个404提示页面
{ path: '*', component: NotFound },
]
})
export default router
路由模式
- Vue支持2种路由模式,分别是
hash
和history
- Vue是单页面应用,多个页面是通过在同一个页面种,使用JS进行切换,而传统的多页面应用,是通过跳转新的页面来跳转,2种应用各有优缺点,它们的区别如下:
单页面、多页面应用对比
开发分类 | 实现方式 | 页面性能 | 开发效率 | 用户体验 | 学习成本 | 首屏加载 | SEO |
---|---|---|---|---|---|---|---|
单页 | 一个HTML页面 | 按需更新,性能高 | 高 | 非常好 | 高 | 慢 | 差 |
多页 | 多个HTML页面 | 整页更新,性能差 | 中等 | 一般 | 中等 | 快 | 优 |
- 根据它们的优缺点,可以分类出,不同场景使用哪种方式
- 单页面应用:系统类网站、内部网站、文档类网站、移动端站点
- 多页面应用:公司官网、电商类网站
实现细节
- hash模式,使用的是H5的新特性,当路径中存在的是
#/
时,浏览器不会跳转,而是发生hashchange
事件,Vue就是监听这个事件来监听路径变化,再进行路径匹配,切换到对应的组件页面。缺点是URL比较丑,莫名其妙出现了个#/
在前面 - history模式,浏览器一开始就支持的模式,当路径变化时,浏览器会发出GET请求服务器,访问这个路径的页面,但单页面应用只有一个HTML页面,所以需要服务端进行配置支持,否则不配置的话,会产生404(其实就是让服务端将404时,返回的页面改为
index.html
)
路由配置
- 通过
mode
属性,切换模式是hash
还是history
- 注意:开发阶段,使用webpack,会使用node.js启动一个开发服务器,已经内置了配置,所以没有问题,但是到线上时,使用的是服务端的服务器,就需要进行配置
import Home from '../views/Home'
import Search from '../views/Search'
import NotFound from '../views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
// VueRouter插件初始化
Vue.use(VueRouter)
// 创建了一个路由对象
const router = new VueRouter({
/*
路由的路径有#,看起来不自然,,能否切成真正路径形式?
hash路由(默认) 例如: http://localhost:8080/#/home
history路由(常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持)
*/
// hash路由,默认值
// mode: "hash",
// 好看
mode: "history",
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
export default router
编程式导航-两种跳转方式
- 上面提到的
导航链接跳转
,都是声明式导航,也就是使用标签来跳转,也就是使用router-link
标签。而编程式导航,就是通过JS调用VueRouter的API来进行跳转 - 编程式导航,还支持命名导航,也就是路由映射时,给映射关系配置一个名称,跳转时使用名称来代替具体路径,这样更好管理路由
路由配置
- 命名路由,通过给映射关系,配置一个
name
属性,也就是给路由设置了一个名称,后面页面跳转使用这个名称,就能跳转,而不需要在页面跳转时写路径
import Home from '../views/Home'
import Search from '../views/Search'
import NotFound from '../views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
// VueRouter插件初始化
Vue.use(VueRouter)
// 创建了一个路由对象
const router = new VueRouter({
// 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
mode: 'history',
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
// 命名路由,通过name属性指定路由的名称,后续则通过指定这个名称去跳转
{ name: 'search', path: '/search/:words?', component: Search },
{ path: '*', component: NotFound }
]
})
export default router
跳转方式
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input @keyup.enter="goSearch" type="text" />
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/程序员">程序员</router-link>
</div>
</div>
</template>
<script>
export default {
name: "HomePage",
methods: {
goSearch() {
// ------- 编程式导航,路径跳转 -------
// 适合路径比较短,直接在这里写上路径
// 写法一,直接传入路径
this.$router.push("/search");
// 写法二,通过配置对象的path属性指定路径
this.$router.push({
path: "/search",
});
// ------- 编程式导航,命名路由跳转 -------
// 适合路径比较长的,将路径放到路由配置文件中,在这里使用名称去找到对应的路径
// 通过配置对象的name属性,配置命名路由的名称
this.$router.push({
name: "search",
});
},
},
};
</script>
<style>
// ...
</style>
编程式导航-跳转传参
上面的编程式导航,没有传参,接下来就来看看怎么传参
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input @keyup.enter="goSearch" v-model="inpValue" type="text" />
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/程序员">程序员</router-link>
</div>
</div>
</template>
<script>
export default {
name: "HomePage",
data() {
return {
inpValue: "",
};
},
methods: {
goSearch() {
// 方式一:路径路由,使用查询参数,传递参数
this.$router.push(`/search?words=${this.inpValue}`);
// 方式二,路径路由,使用配置对象
this.$router.push({
path: "/search",
query: {
words: this.inpValue,
},
});
// 写法三,动态路由-直接拼路径的方式
this.$router.push(`/search/${this.inpValue}`);
// 写法四,动态路由-配置对象的方式
this.$router.push({
path: `/search/${this.inpValue}`,
});
// 写法五,命名路由的方式
this.$router.push({
name: "search",
params: {
words: this.inpValue,
},
});
},
},
};
</script>
<style>
// ...
</style>
keep-alive
一级路由(App.vue)
<template>
<div class="h5-wrapper">
<!--
Vue组件一般什么时候会被销毁掉?
- vue实例.$destroy(),手动销毁组件
- v-if,是将DOM元素直接从DOM树中移除,所以组件也被销毁了
- 路由切换,例如Tab切换,离开了组件,就会被销毁
-->
<!-- keep-alive,缓存组件,保证页面跳转到详情页后返回,一级路由的所有组件都不会被销毁 -->
<!-- 一级路由,包括 LayoutPage 和 ArticleDetailPage,但我们指向Layout被会缓存,详情页不要被缓存 -->
<!-- 注意:默认Vue组件的名称就是文件名,但如果可以在组件身上指定 name 属性,就是我们手动指定的 name 属性,而不是文件名 -->
<!-- (常用)include属性,则指定要被缓存的组件,就是要缓存谁,属性值是要被缓存的组件名数组,是组件名,而不是文件名!!! -->
<!-- exclude属性,配置不被缓存的组件,就是不要缓存谁,和include属性是相反的,这2个属性一般不会同时用,只会选其一 -->
<!-- max属性,缓存组件的实例数量 -->
<!--
注意:
一般我们使用 exclude 属性,它是默认所有都不缓存,只缓存我们指定的组件,节省内存
而 include 属性,是所有组件都缓存,然后不缓存我们指定的组件,会导致缓存的组件太多,导致性能问题
-->
<keep-alive :include="keepAliveArr">
<!-- 一级路由-出口 -->
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name: "h5-wrapper",
data() {
return {
// 缓存的组件的组件名数组
keepAliveArr: ["LayoutPage"],
};
},
};
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
<style lang="less" scoped>
.h5-wrapper {
.content {
margin-bottom: 51px;
}
.tabbar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
display: flex;
background: #fff;
border-top: 1px solid #e4e4e4;
a {
flex: 1;
text-decoration: none;
font-size: 14px;
color: #333;
-webkit-tap-highlight-color: transparent;
&.router-link-active {
color: #fa0;
}
}
}
}
</style>
二级路由(Layout.vue)
<template>
<!-- 一级路由页面-首页 -->
<div class="h5-wrapper">
<div class="content">
<!-- 组件缓存,缓存所有Tab页面 -->
<keep-alive>
<!-- 二级路由-出口, -->
<router-view></router-view>
</keep-alive>
</div>
<!-- 底部TabBar -->
<nav class="tabbar">
<router-link to="/article">面经</router-link>
<router-link to="/collect">收藏</router-link>
<router-link to="/like">喜欢</router-link>
<router-link to="/user">我的</router-link>
</nav>
</div>
</template>
<script>
export default {
// 组件名(如果没有配置 name,才会找文件名作为组件名)
name: "LayoutPage",
// 注意:一般组件被缓存了,反复进入该组件,组件的created、mounted、destroyed,就只有第一次的时候被执行
// 默认组件是不会被缓存的,每次进出页面,都会重新加载组件,声明周期回调函数就会被执行,但缓存后就不会了
// 被缓存后,会多2个生命周期函数回调,分别是 activated 和 deactivated
created() {
console.log("created 组件被加载了...");
},
mounted() {
console.log("mounted 组件的DOM渲染完毕了...");
},
destroyed() {
console.log("destroyed 组件被销毁了...");
},
activated() {
console.log("activated 组件被激活了,也就是页面可见了...");
},
deactivated() {
console.log("deactivated 组件失活,也就是页面不可见了...");
},
};
</script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
<style lang="less" scoped>
.h5-wrapper {
.content {
margin-bottom: 51px;
}
.tabbar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
display: flex;
background: #fff;
border-top: 1px solid #e4e4e4;
a {
flex: 1;
text-decoration: none;
font-size: 14px;
color: #333;
-webkit-tap-highlight-color: transparent;
}
a.router-link-active {
color: orange;
}
}
}
</style>