前言
点进这篇文章的童鞋,俺默认你已经对 路由、前端路由等有了一定的了解,如果对路由还不太了解的童鞋,点我可以先去了解一下路由、前端路由、后端路由等等的概念,再来食用这篇文章效果会更佳哦~
起步…走
vue-router
是 Vue 核心的一个插件,是 Vue 官方提供的一个路由管理器,必然属于前端路由的范畴。
我们会一步步实现一个简单的 HelloWorld ,快速认识一下它。
第一步我们引入 vue 、 vue-router ,这里直接使用 CDN 了,如果网速不好的同学可以将这两个文件保存到本地然后引入。
<head>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
我们首先创建一个 Vue 实例 ,这是必不可少的。
<div id="#app"></div>
<script>
const vm = new Vue({
el:'#app'
})
</script>
紧接着我们创建两个简单的局部组件,并且注册给 Vue 实例
const position = {
template:`<h2>position...</h2>`
}
const home = {
template:`<h2>home...</h2>`
}
const vm = new Vue({
el:'#app',
components:{
// 注册组件
home,
position
}
})
接下来我们配置一下链接,在 app 元素内
添加以下内容,router-link
默认会渲染成一个 a
标签,to
相当于 href
属性,可以配置跳转到对应的路由,路由地址可以在这里定义。
<router-link to="/home">首页</router-link>
<router-link to="/position">职位</router-link>
接下来需要定义一个 <router-view>
,表示路由在切换时,内容显示在这里。
<router-view>
// 路由切换时在这里显示对应组件的内容....
</router-view>
接下来我们就可以配置 vue-router 了!首先创建一个vue-router
的实例。配置一个 routes
选项,是一个路由对象的数组。
路由对象第一个参数代表的是路由匹配的地址,第二个参数是跳转后显示哪个组件。
// 组件....
// vue-router 实例
const router = new VueRouter({
routes:[
{path:'/home',component:home},
{path:'/position',component:position}
]
})
// vue 实例....
光创建了 vue-router 实例还不行,还需要和 Vue 实例做一个关联,非常简单
const vm = new Vue({
el: '#app',
components: {
home, position
},
router: router, // 关联 vue-router ,因为key-value一致,这里可以简写为 router
})
到此为止,一个简单的 vue-router HelloWorld 就搞定了。我们可以打开浏览器看一下效果。
继续完善
这个样子太丑了,俺想继续完善、优化一下代码,加入一些新的功能,例如:
1、网页第一次加载的时候就显示 首页
的内容
2、当连接被选中时,俺希望添加一个样式,表示当前是选中状态
3、网页 URL 地址有一个很难看很丑的 # ,俺想…去掉…
其实上述几个功能在我们 vue-router 中实现起来轻轻松松,赶赶单单。
1、第一次加载时显示 首页
我们先来说第一个,在 vue-router 中有一个重定向的概念,可以在访问 / 根目录的时候让它重定向到我们需要显示的路由。
我们在 vue-router 实例中,再配置一个匹配 /
的路由 ,然后使用 redirect
这个选项可以让它进行重定向。
const router = new VueRouter({
routes: [
{ path: '/home', component: home },
{ path: '/position', component: position },
{ path: '/', redirect: '/home' }
]
})
ok! 这就完了? 是的这就完了,在 vue-router 中就是这么简单!
2、连接被选中时,添加一个样式
我们首先需要在 css 中创建这么一个样式,例如说就叫做 active
.active{
color: #fff;
background: #f78;
font-weight: bold;
}
然后我们需要在 router-link
上添加一些代码,表示选中时使用 active
这个样式。注意: active-class
是固定写法!
<router-link to="/home" active-class="active">首页</router-link>
<router-link to="/position" active-class="active">职位</router-link>
我们再来看一下效果
so…easy…
3、优化 URL 地址
首先观察一下 url 地址,emmm 确实有一个挺让人烦心的 #
,这其实是 vue-router 的一种默认路由模式 hash
模式。
还有另外一种 history
模式,这种 history
模式可以让这烦人的 # 号干掉,那我们应该怎么样去配置路由模式呢?
其实也是非常简单的,在 vue-router 实例中有一个配置选项 mode
是用来配置路由模式的。
const router = new VueRouter({
mode: 'history', // 默认不写是 hash,可以设置 history
routes: [
{ path: '/home', component: home },
{ path: '/position', component: position },
{ path: '/', redirect: '/home' }
]
})
重新打开浏览器看一下 URL ,就找不到 # 啦,但是现在有出现了一个问题,我们首次打开页面的时候并没有显示 首页
的内容
仔细观察 URL ,俺猜测原因可能是因为没有匹配到 /
,所以没有重定向。我们稍微修改一下代码看看
{ path: '/', redirect: '/home' } ---> { path: '*', redirect: '/home' }
这个问题算是解决了,但是细心的同学可能就发现了,使用 history
这种模式,在刷新页面的时候会有问题!
这种刷新的问题,根据官方文档可以看到,我们如果要使用 history 模式,需要在后端进行配置,具体配置方法请参考官方文档后端配置栗子
嵌套路由
啥是嵌套路由呢?就拿 HelloWorld 栗子来说,假如说在首页中不仅仅显示的是
home...
,显示的是一个导航,点击导航选项的时候,会显示对应的内容。在职位中,显示的也不仅仅是position...
,显示的同样是一个列表或者多个链接,点击对应的连接时,显示不同的内容。 类似于这样的场景就会用到 嵌套路由。
接下来我们就用 嵌套路由
实现上述的功能
第一步,需要修改一下 home
组件 和 position
组件中的内容
const position = {
template: `
<div>
<nav>
<router-link to="/position/add" active-class="active">添加职位</router-link>
<router-link to="/position/update" active-class="active">修改职位</router-link>
<router-link to="/position/del" active-class="active">删除职位</router-link>
</nav>
// 点击添加/修改/删除后内容显示到 router-view 中
<router-view></router-view>
</div>
`
}
const home = {
template: `
<div>
<nav>
<router-link tag="li" to="/home/menu1" active-class="active">Menu one</router-link>
<router-link tag="li" to="/home/menu2" active-class="active">Menu two</router-link>
<router-link tag="li" to="/home/menu3" active-class="active">Menu three</router-link>
<router-link tag="li" to="/home/menu4" active-class="active">Menu four</router-link>
</nav>
// 内容也是显示到这里
<router-view></router-view>
</div>
`
}
细心的同学可能发现了,在 home 组件模板中,router-link
上使用了一个 tag
属性,这个属性的意思是将 router-link
以什么样的标签进行渲染,这里指定了 li
,也就是说, router-link 渲染出来是 li 标签
。如果不设置 tag
属性,router-link
默认是以 a
标签的形式进行渲染的。
看一下浏览器的结果就知道啦~
页面 ok 了,接下来我们配置一下路由,在路由对象中,有一个 children
配置选项,它是一个数组,可以配置多个子路由对象。
{
path: '/home',
component: home,
children: [
{
// 这里的 path 可以写绝对路径 /home/menu1 ,也可以写相对路径 menu1
path: 'menu1',
component: { // 组件这里当然可以单独抽取成一个对象,也可以直接写到这里,没啥毛病.
template: `<p>menu1的内容...1</p>`
}
},
{path: 'menu2',component: {template: `<p>menu2的内容...2</p>`}},
{path: 'menu3',component: {template: `<p>menu2的内容...3</p>`}},
{path: 'menu4',component: {template: `<p>menu2的内容...4</p>`}},
]
}
配置 position
的子路由
{
path: '/position',
component: position,
children: [
{ path: 'add', component: { template: `<p>添加职位啦....</p>` } },
{ path: 'update', component: { template: `<p>修改职位啦....</p>` } },
{ path: 'del', component: { template: `<p>删除职位啦....</p>` } },
]
},
这就是嵌套路由的配置方式啦,其实也是蛮简单的嘛~ 打开浏览器看一下效果!
如果我们希望网页一加载就显示 首页中的第一个导航选项的内容,可以跟上面一样,修改匹配/
的路由,然后使用 redirect
重定向到对应的路由就可以了。
{ path: '/', redirect: '/home/menu1' }
动态路由
高大上的名字总是朴实无华…且枯燥… 哈哈哈,开个玩笑。 啥是动态路由呢? 其实就是匹配路由路径里动态的参数。 举个例子
{path: '/position/del/:id'}
:id
就是一个动态的参数,可以传递任意值。
删除内容的时候,总是需要一个唯一标识来确定删除的是哪一条内容。我们可以在路由匹配里面配置
{ path: 'del/:id', component: { template: `<p>删除职位啦....</p>` } },
修改路由对应的连接,我们可以传递一个参数
<router-link to="/position/del/10010" active-class="active">删除职位</router-link>
那怎么才能取到这个 10010
数据呢,我们可以通过 $router.params
获取到动态参数对象
修改组件中的代码,获取一下这个 id
{ path: 'del/:id', component: { template: `<p>删除职位啦....{{$route.params}}</p>` } },
传递多个值
<router-link to="/position/del/10010/29312/" active-class="active">删除职位</router-link>
配置path
,获取的时候也是一样 ,使用 $router.params
{ path: 'del/:id/:userid', component: { template: `<p>删除职位啦....{{$route.params}}</p>` } },
命名路由
说白了就是给 路由起个名字,可以在路由对象中通过
name
选项配置路由的别名。
{ path: 'del/:id/:userid', name : 'del',component: { template: `<p>删除职位啦....{{$route.params}}</p>` } },
修改 <router-link>
的代码
<router-link :to="{name:'del',params:{id:10010,userid:29312}}" active-class="active">删除职位</router-link>
结果是一样的
命名视图
官方介绍
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。
<router-view>
就是视图, 我们来简单实现以下上述的功能。
视图代码
<div id="app">
<router-link to="/home">首页</router-link>
<div class="container">
<!--可以给视图起一个名字,如果不设置 name ,默认是 default-->
<router-view class="sidebar"></router-view>
<router-view name="main" class="main"></router-view>
</div>
</div>
创建两个组件
var sidebar = {
template: `<h2>侧边导航</h2>`
}
var main = {
template: `<h2>主内容</h2>`
}
配置 路由对象
routes: [
{
path: '/home',
components: {
default : sidebar,
main: main // 第一个 main 是 router-view 的name 属性值,第二个 main 是组件的名字
// 也可以直接简写为 main
}
}
]
命名视图其实也就是给视图起名字区分一下,如果不写 name 属性,默认是 defalult
命名视图也是可以嵌套的,其实和嵌套路由差不多,有兴趣的同学可以点我去看一下官方文档的介绍,我这里就不演示了。
路由组件传参
路由传参这块在 Vue 整个路由中是非常重要的一部分,也是面试经常要考的。
在上述动态路由中,我们给 删除
路由设置了2个动态路由参数,在使用的时候通过 $route.params
这种方式获取的参数,使用$route
这种方式会增加路由和组件的耦合性,因此我们需要使用 props
进行解耦。
使用 props 解耦
修改路由配置对象设置 props:true
,这种方式也叫 布尔模式
{ path: 'del/:id/:userid', props: true, name: 'del', component: { template: `<p>删除职位啦....</p>` } }
在组件中,通过 props 来接收参数,然后使用即可
component: {
props:["id","userid"],
template: `<p>删除职位啦....{{id}}--{{userid}}</p>`
}
打开浏览器查看运行结果,一样可以获取到动态参数的值,但是这样写就降低了组件和路由之间的耦合性
对象模式
我们可以给 props 传递一个对象,修改 修改职位
的路由配置信息
{
path: 'update',
props: { name: 'xiaosai', age: 18 },
component: {
props: ["name", "age"],
template: `<p>修改职位啦....{{name}}---{{age}}</p>`
}
}
效果
传递对象时需要注意: 只有 当 props 是静态的时候有用,动态参数是获取不到的。
函数模式
可以创建一个 函数 返回
props
, vue 会自动为该函数注入一个 route 对象,可以通过route.params.xxx
获取参数。
修改 修改职位
路由配置信息
{
path: 'update/:name/:age',
props: route => ({
name: route.params.name + '111', // 对参数做一些简单的处理
age: parseInt(route.params.age) + 5
}),
component: {
props: ["name", "age"],
template: `<p>修改职位啦....{{name}}---{{age}}</p>`
}
},
修改 <router-link>
内容
<router-link to="/position/update/zhangsan/21" active-class="active">修改职位</router-link>
编程式导航
前面也说过
<router-link>
默认会渲染成a
标签,如果想渲染成li
可以使用tag
来设置,如果是其他元素呢,按钮、图片等等… 这时候显然使用<router-link>
就不行了。我们可以使用原生的 html 标签,使用$router
的 一些方法手动来实现路由的跳转,这就是编程式导航。
我们还是以最初的栗子来演示,把 首页、职位
分别替换成 按钮和图片,我们直接使用原生的 html 标签替换 <router-link>
<button>首页</button>
<img src="https://img-blog.csdnimg.cn/20200406114911306.png" alt="">
先打开网页看一下效果
可以看到,按钮和图片都添加上去了,但是我们点击是没有任何效果的,需要给按钮和图片分别添加点击事件。
<button @click="btnHandler">首页</button>
<img src="https://img-blog.csdnimg.cn/20200406114911306.png" alt="" @click="imgHandler">
在 vue 实例中实现这两个函数
methods: {
btnHandler() {
this.$router.push('/home')
},
imgHandler() {
this.$router.push('/position')
}
}
可以通过 $router
访问 路由的实例,push
方法会在 history 栈中添加一个新纪录,可以通过浏览器的前进后退按钮进行操作。
push 方法的参数
- 字符串
- 对象
- 命名的路由
- 带查询参数的路由
go 方法
使用
$router.go(step)
可以实现前进后退几步
的操作,我们直接用一下试试
再创建两个按钮,并且实现点击前进和后退的功能
<button @click="$router.go(1)">前进</button>
<button @click="$router.go(-1)">后退</button>
导航守卫
官方文档第一句话
“导航” 表示路由正在发生改变
,而守卫的意思说白了其实就是钩子函数
。合起来就是可以用来监听到路由发生改变的钩子函数
, 这样是不是更好理解了?守卫分为三类全局守卫
、路由守卫
、组件守卫
。
全局守卫有以下三个
router.beforeEach()
在路由跳转之前会执行该函数,参数是一个回调函数,并且会注入三个参数,分别为 from
to
next
router.beforeEach((to, from, next) => {
console.log("to:", to);
console.log("from:", from);
})
因为 beforeEach 是全局守卫,所以每个路由在跳转之前都会被执行。
from
就是从哪个路由来,to
要跳转到哪里去。可以看到,此时内容并没有显示出来,因为我们没有执行 next
router.beforeEach((to, from, next) => {
console.log("to:", to);
console.log("from:", from);
next(); // 执行 next 放行
})
我们一般会使用这种方式来做一些权限的验证。
router.beforeResolve()
全局解析守卫
引用官方文档的解释
vue 2.5 版本新增的内容,和
beforeEach()
有点类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
router.beforeResolve((to, from, next) => {
console.log("to:", to);
console.log("from:", from);
next();
})
router.afterEach()
全局后置守卫,该守卫的回调函数中只有两个参数
from
、to
,因为这时候跳转已经完成了,所以没有next
参数了。
router.afterEach((to, from) => {
console.log("afterEach:", to, from);
})
路由独享守卫 beforeEnter()
可以在路由上直接定义该守卫
修改 add
路由配置
{
path: 'add',
component: {
template: `<p>添加职位啦....</p>`
},
beforeEnter: (to, from, next) => {
console.log("beforeEnter:", from, to);
next()
}
},
组件守卫
可以在路由组件中定义守卫钩子函数
beforeRouteEnter()
在渲染该组件的对应路由被 confirm 前调用,需要注意的是 不能获取组件实例 this
,此时组件实例还没有被创建。
beforeRouteUpdate()
路由改变的时候
举例来说,对于一个带有动态参数的路径 /foo/:id
,在 /foo/1
和 /foo/2
之间跳转的时候,
由于会渲染同样的 Foo
组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
可以访问组件实例 this
beforeRouteLeave()
路由离开组件之前,可以访问组件实例 this
{
path: 'add',
component: {
template: `<p>添加职位啦....</p>`,
beforeRouteEnter: (to, from, next) => {
console.log("beforeRouterEnter");
next();
},
beforeRouteUpdate: (to, from, next) => {
console.log("beforeRouteUpdate");
next();
},
beforeRouteLeave: (to, from, next) => {
console.log("beforeRouteLeave");
next();
}
}
},
修改 position 组件模板中的内容,添加一个 按钮,并且绑定点击事件,手动切换路由
const position = {
template: `
<div>
<nav>
<router-link to="/position/add/1001" active-class="active">添加职位</router-link>
<button @click = "handler">update id</button>
<router-link to="/position/update/zhangsan/21" active-class="active">修改职位</router-link>
<router-link to="/position/del" active-class="active">删除职位</router-link>
</nav>
<router-view></router-view>
</div>
`,
methods: {
handler() {
// id 自增1
var path = `/position/add/${++this.$route.params.id}`
// 切换路由
this.$router.push(path)
}
}
}
注意控制台打印的内容.
细节
总结一些小知识点
1、router
、routes
、route
分别是什么
router 是 VueRouter 的实例 , 每个实例中必须配置一个 routes
,他是一个数组,里面可以配置多个路由对象,也就是 route
。
2、怎么处理 404
处理 404 非常简单,在路由的最后配置一个
*
,然后指向一个组件就可以了。
{path : '*',component:{template:`<h2>404 not found</h2>`}}
注意: 异常处理的一定要配置在最后一个
因为路由的匹配顺序是按照定义顺序来匹配的,谁先定义的,就先匹配谁。 试想一下,如果把 *
写在最前面,或者中间, 遇到 * 就不会执行后面的了。
3、正则匹配路由
我们也可以使用正则来匹配路由(其实在处理 404 的时候 * 就是一个正则了…),还有很多高级的匹配模式,有兴趣的小伙伴也可以去看一下它的用法。
总结
到此为止,vue-router 诈看上去可能会稍微有点复杂,没有接触过的会觉得vue-router 作为 vue 核心插件,肯定会有一些难度,其实每个知识点细品以后你会发现,也就这么回事,核心无非就是 跳转、传参…等等 。
写了这么多其实都是一些基础必会的知识点,vue-router 还有一些高级的功能 ,具体用法请参考 官方文档 vue-router进阶部分
如果各位小伙伴觉得文章写得还不错,请动动您的小手指,点个赞,收藏,+关注,您的支持是我最大的动力呢~