路由 vue-router
如何用vue来创建单页应用,路由在单页应用中非常重要
单页应用,技术层面上,只有唯一的一个页面,也就是在index.html里面,会存放整个vue应用,然后使用这个唯一的页面,模拟出可以跳转页面的交互感觉,所以url还是会变化的,而且我们会监听这些url的变化,然会对这个唯一的页面进行重载,所以感觉上,像是在浏览不同的页,但是在后台,始终都只有这一个页面。我们不再需要在应用里,加载从服务器获取到的各个页面,然后用Vue对页面的某一部分进行处理。现在整个应用都是用Vue来控制的,可以用Vue来创建更加复杂和庞大的应用。
使用路由在不同组件之间进行切换,以及动态地对各个部分进行加载
可以把组件想象成普通的可以切换的页面
vue的核心应用场景是应用于多页应用中,用来添加各种插件,或者添加部分内容
-
项目配置
-
在index.html文件里引入Bootstrap样式
-
安装第三方库:vue-router使用单页应用的各种功能和特性
npm install --save vue-router -
引入到应用中main.js,并使用
import VueRouter from 'vue-router'Vue.use(VueRouter); -
设置路由
可以设置在另一个不同的文件中,然后再把那个文件引入到这个main.js里,但还是应该在main.js这里注册,注册在这里的根Vue实例上,尤其是对于创建单页应用
只会有一个根实例,而且只会存在组件间的切换,虽然那样看起来像是他们也有他们自己的实例,但只会有一个由new关键字创建的实例,所以一定要把你的路由注册在这里的根实例中,否则就无法达到期望的效果,程序也会报错。真实的应用中,经常会在外部进行设置,因为分好多种不同的路由。
可额外设置一个routes.js文件(文件层次与main.js并列)
//routes.js 文件中每个对象都代表一个路由,当然需要对他进行注册 //每个路由都需要一个path属性,这里的内容会在之后添加在你的域名url之后,component代表每次访问这个路径时需要加载的组件(需要引入) import User from './components/user/User.vue'; export const routes = { //若路径为空,则是首先进行加载的根路由 {path:'',component: Home} {path:'/user',component: User} }//main.js 注册路由/引入路由文件(其中命名了常量输出) //使用带花括号的语法,引入routes,因为在里面导出的是一个常量 import {routes} from './routes' //引入的插件需要注册,存储为一个常量,传递一个对象作为参数,用来设置我的路由 const router = new VueRouter({ //es6特性,,如果把它传给这个对象的话,会自动遍历这个对象,这样能得到routes里面所有的键值对(显式写下这个同名的键值对也可以实现,是一种简写) routes }) //vue实例中,把router添加进去,键值相同,可以进行简写,终于成功注册了路由 new Vue({ el:'#app', router, render:h=>h(App) })需要一个地方进行渲染,也就是在HTML代码的某一个地方,让vue加载相应的组件,这个组件不会替换掉整个页面
//App.vue //主页的模板 //标注一下应该在哪里进行加载当前组件 //该组件告诉路由,在这里来加载那个该加载的组件 <router-view></router-view>根路由/#/,vue-router默认的工作模式是 #模式
可以直接添加url来进行页面跳转
在url内看到了#,这是vue-router自动添加的,是vue-router的默认配置,在其他单页面应用内也会看到这种带#的url.
如果是不加#的普通url,用户每次点击链接,都会向服务器发送请求,这是浏览器或者说是Web的运行机制。
当我们想在单页面应用内部处理路由时,把请求发送到服务器就有问题了,我们不想把路由交给服务器处理,而是想在本地处理。不过在第一次访问页面时,需要访问服务器以获取整个单页面应用本身(index.html这个文件),然后由单页面应用接管url的余下部分,而不再由服务器处理剩下的url
#就能实现这个功能,#之前的url才会被发送到服务器,然后服务器返回的是index.html,#之后的部分则会传给正在运行的JavaScript应用,由应用来处理,然后应用就能够获取这里的路径,这样的机制运行良好,只不过还要对服务器进行特殊的配置,才能返回正确的结果。
有一部分的配置要在服务器上进行,服务器在任何情况下都返回index.html,甚至在404错误的情况下也是返回index。html
因为我们的Vue应用就是在index.html文件内启动的
//index.html <script src="/dist/build.js"></script>只有这样配置服务器,Vue才能解析URL,然后接管路由的处理,这样使用路由不仅方便而且美观
const router = new VueRouter({ routes, //没有#的url模式(默认的模式是hash)需要配置服务器,他在任何情况下,返回的都是index.html,否则就不会正常运行 mode:'history' })
-
-
路由链接导航
a标签,href属性,他总是会把请求发送到服务器,尽管服务器可以处理好,但他会限制设置已访问的链接的方式,还意味着它一定会向服务器发送一个请求。如果我已经在链接指向的页面上了,直接不发送请求会更好一点,因为我知道自己不想离开这个应用。
Vue提供的可以代替a标签的组件
router-link它最后会被渲染为a标签,可包裹内容,传入to属性,这个属性可以用冒号进行动态绑定,to属性是链接跳转的目标,可以是相对路径,比如user,它会被添加到当前url上组成实际访问的链接。或者是/user/home.带/的链接是添加到域名后面组成实际访问的链接,而不是添加到当前url的后面。组件导入到App.vue中
//App.vue
<template>
<app-header></app-header>
</template>
<script>
import Header from './component/Header.vue';
export default{
components:{
appHeader:Header
}
}
</script>
-
定义活动链接
更改router-link渲染的标签类型,在router-link标签上添加tag属性,例,渲染为li标签
router-link会在内部嵌套的任意内容上设置链接
exact覆盖了默认的前缀匹配,要求完全匹配
//router-link会在li标签上设置链接 <router-link to="/" tag="li" active-class="active" exact><a>Home</a></router-link> -
通过代码导航(强制导航)
用JavaScript实现跳转
//User.vue <script> export default{ methods:{ navigateToHome(){ //调用push添加跳转,因为我们要把这个路由添加到历史记录中,以便让浏览器的前进后退功能可以正常使用,push用法,传入要跳转的路由,也可以传入一个对象 this.$router.push('/'); } } } </script> -
配置路由参数
要在路径里面传入动态的片段,只需要在路由里面加上一个冒号,,然后就是动态内容的名称,这里就是id,名称是为了之后能够访问到它
//routes.js
import User from './components/user/User.vue';
export const routes = {
//若路径为空,则是首先进行加载的根路由
{path:'',component: Home}
{path:'/user/:id',component: User}
}
//Header.vue
<router-link to="/user/10" tag="li" active-class="active" exact><a>Home</a></router-link>
获取、使用路由参数
//User.vue
<script>
export default{
data(){
return{
//访问Vue提供的$route路由实例,可以用它实现跳转,$route是当前加载的路由,他有一个params属性,也是一个对象,他保存的是传入参数的键值对
//访问传入的参数/数据的方法
id:this.$route.params.id
}
}
methods:{
navigateToHome(){
//调用push添加跳转,因为我们要把这个路由添加到历史记录中,以便让浏览器的前进后退功能可以正常使用,push用法,传入要跳转的路由,也可以传入一个对象
this.$router.push('/');
}
}
}
</script>
响应路由参数改动
如果已经位于User组件页面上的话,User组件不会被重新创建,只有path 变了(id不同)但加载的还是原来的组件,不会重新创建组件,他会为了节省资源,而保留原有的组件,如果这个组件的一部分需要改变的话,比如这里的id,可以通过监听params的变化,根据params的值更新页面上的部分内容
侦听属性,侦听$route
//User.vue
<script>
export default{
data(){
return{
id:this.$route.params.id
}
},
watch:{
//侦听$route,这里是一个函数,应该传入两个由vue-router提供的函数to(表示我们要跳转到的路由) 和 from(表示原路由)to是一个路由
'$route'(){
this.id = to.params.id;
}
}
}
</script>
配置子路由(嵌套路由)
在User组件内添加子路由,以便在User组件内加载其他组件,需要给嵌套的父路由添加一个属性children,这个属性是一个路由数组,他存放了要嵌套在/user这个根路由下的子路由,所以要在这个数组里传入几个javaScript对象,每个对象都有path属性,重要的是,如果这里的path是/开头的,这个路径就会直接加到域名后面,要是不加/,这个路径就会加到父路由的路径的后面,这两种方法你可以任选其一。
你可以使用以域名开头的路径,仅仅在程序里嵌套子路由。也可以在路径里体现父子嵌套关系,从而在程序和逻辑两个方面都体现嵌套关系
//routes.js
import User from './components/user/User.vue';
export const routes = {
//若路径为空,则是首先进行加载的根路由
{path:'',component: Home}
{path:'/user',component: User,children:[
{path:'',component: UserStart},
//没有把这两个路由嵌套在UserDetail内,因为我不想在同一个页面里面再加载UserDetail
{path:':id',component: UserDetail},
{path:':id/edit',component: UserEdit}
]}
}
还需要在页面上指定加载三个子组件的位置,因为他们不会加载在App.vue内的router-view标签上,因为这个标签是应用的根路由。
我们又添加了嵌套路由,所以需要在User.vue内设置一个新的router-view标签
//User.vue
<router-view></router-view>
嵌套路由导航
//UserStart.vue
<ul>
//在实际应用中可以使用数组来生成这些链接,也可以用冒号动态绑定他们,绑定到包含path的属性
<router-link tag="li" to="/user/1"></router-link>
</ul>
在UserDetail组件内提取传入的参数
//UserDetail.vue
//因为这个路由在跳转到其他路由之前不会更新,所以就不用添加侦听属性
//不用监听$route的变动,因为如果我在UserDetail组件内,不可能点击某个元素就加载id为2的组件,如果手动输入url,也会重新创建一个组件,所以就不用侦听$route.params的变化,因为在这个组件里他不可能变化,无论如何,这个组件都会被重新创建
<div>id:{{$route.params.id}}</div>
更动态的配置路由链接
//UserDetail.vue
//手动拼写路径,:to动态传递参数
<router-link tag="button" :to="'/user/'+$route.params.id+'/edit'"></router-link>
命名路由
//routes.js
//可以在这里加上name属性,给这个路由命名,后续可以直接通过他的名字来找到它
export const routes = {
//若路径为空,则是首先进行加载的根路由
{path:'',component: Home}
{path:'/user',component: User,children:[
{path:'',component: UserStart},
//没有把这两个路由嵌套在UserDetail内,因为我不想在同一个页面里面再加载UserDetail
{path:':id',component: UserDetail},
{path:':id/edit',component: UserEdit,name:'userEdit'}
]}
}
对于任何只要是设置有链接的页面,比如用户详情页
//UserDetail.vue
//:to动态传递参数 ,可以传入一个对象,而不需要使用字符串,对象中可以设置一些属性,决定跳转的路径
参数设置可以应用在所有的router-link里面,总是可以给to传递对象,还可以通过传递一个设置了path属性的对象来实现
<router-link tag="button" :to="{name:'userEdit',params:{id:$route.params.id}}"></router-link>
<router-link tag="button" :to="{path:'user'}"></router-link>
使用查询参数
查询参数就是位于URL后面的参数,总是位于末尾,以一个问号开头,然后会有类似于a=100&b=hello这样的东西。可以用来存储重定向路由,在网页重定向到登陆页面之后,还想要回到之前的那个页面等类似的功能。可以在路由的to属性里,直接设置你想要的路由,也可以使用对象语法来设置,键值对
//UserDetail.vue
<router-link tag="button" :to="{name:'userEdit',params:{id:$route.params.id},query:{locale:'en',q:100}}"></router-link>
</router-link>
//UserEdit.vue
在目标页面提取查询参数
<p>locale:{{$route.query.locale}}</p>
可以使用侦听器来进行动态加载
多路由视图/命名路由视图
不仅可以对路由命名,同时也能对路由视图进行命名
//App.vue
<router-view name="header-top"></router-view>
//未命名的是默认的路由视图,他会对所有的东西进行加载
<router-view></router-view>
<router-view name="header-bottom"></router-view>
//routes.js
//可以在这里加上name属性,给这个路由命名,后续可以直接通过他的名字来找到它
export const routes = {
//若路径为空,则是首先进行加载的根路由
{path:'',components:{
default:Home,
'header-top':Header
'header-bottom':
}}
{path:'/user',components:{
default:User,
'header-bottom':Header
},children:[
{path:'',component: UserStart},
//没有把这两个路由嵌套在UserDetail内,因为我不想在同一个页面里面再加载UserDetail
{path:':id',component: UserDetail},
{path:':id/edit',component: UserEdit,name:'userEdit'}
]}
}
命名视图,可以有效地保存页面上的某一部分的布局,或者是某一部分HTML代码,可以根据你所访问的不同路由,动态地来渲染应用中的各个部分
重定向
当输入的路径无法在任何路由设置里找到时,可重定向到特定页面
//routes.js
//可以在这里加上name属性,给这个路由命名,后续可以直接通过他的名字来找到它
export const routes = {
...其他具体的路由
//访问前者,重定向到后者目标路径(redirect属性的内容可以设置成一个普通的对象)
{path:'/redirect-me',redirect:'/user'}
}
配置“Catch All"路由通配符
用户输入一个应用里不存在的路由
//routes.js
//可以在这里加上name属性,给这个路由命名,后续可以直接通过他的名字来找到它
export const routes = {
...其他具体的路由
//都无法匹配,来到最后一个路由
//通配符会匹配所有没有被匹配到的路由
{path:'*',redirect:'/user'}
}
路由动画过渡
让一个路由切换到另一个路由呈现动画过渡效果
//App.vue
<router-view name="header-top"></router-view>
//让这个路由试图里面的组件呈现动画效果
<transition name="slide" mode="out-in">
<router-view></router-view>
</transition>
<router-view name="header-bottom"></router-view>
传递Hash Fragment
定位到一个页面特定的部位,会在url后面加上#,然后加上HTML元素
锚点的名字,也就是锚点的id
想要在进入页面时,控制页面滚动的位置
//UserDetail.vue
<template>
//to动态绑定对象
<router-link tag="button" :to="link"></router-link>
</template>
<script>
export default {
data(){
return {
link:{name:'userEdit',
params:{
//vue实例中需要使用this
id:this.$route.params.id
},
query:{
locale:'en'
,q:100
},
hash:'#data'
}
}
}
}
</script>
控制卷屏行为
通过vue-router配置滚动行为(在应用内跳转,页面就可以滚动,但直接输入url是没有效果的)
//main.js
import {routes} from './routes'
const router = new VueRouter({
routes,
mode:'history',
//如果点击了后退按钮和使用了滚动条滚动,浏览器会自动保存位置,我们也可以在这里使用这个位置
scrollBehavior(to,from,savedPosition){
//该方法需要返回一个包含x,y坐标的对象
//x:0,y:0会让页面滚动到顶部
//或者是返回有selector属性的对象,值是标签元素的id之类的选择器
if(savedPosition){
return savedPosition
}
if(to.hash){
return {selector:to.hash}
}
return {x:,y:}
}
})
new Vue({
el:'#app',
router,
render:h=>h(App)
})
使用守卫来保护路由
控制用户能否访问一个路由,或者能否离开
检测:
-
在用户进入一个新的路由之前,
-
以及用户想要离开之前
-
在用户进入一个新的路由之前,
使用”beforeEnter守卫“
检测是否允许用户访问特定的路由,只能访问源路由,不能访问要跳转的组件
-
main.js全局控制
可以在把配置的路由传递到实例内之前,执行一个beforeEach()方法(在每次路由跳转之前执行,参数是一个函数,这个函数有三个参数)
在全局router实例上设置,然后传递给Vue实例的beforeEach
//main.js router.beforeEach((to,from,next) =>{ //调用next方法让路由操作继续进行,如果不调用Next Vue就会认为路由操作不应该继续执行,就会直接退出,无法打开目标页面 //调用next(false),来终止路由跳转,让用户还停留在原来的页面 //给next传入一个路径,或者是对象,可以实现路由重定向 //三种用法 next() })-
单个路由控制
有的时候,仅仅需要保护特定的路由,例UserDetail,
可以添加 beforeEnter属性,也hi是一个函数,可以把它存在其他文件里然后再导入,和全局的beforeEach的参数一样
-
-
-
//routes.js
export const routes = {
//若路径为空,则是首先进行加载的根路由
{path:'',components:{
default:Home,
'header-top':Header
'header-bottom':
}}
{path:'/user',components:{
default:User,
'header-bottom':Header
},children:[
{path:'',component: UserStart},
//没有把这两个路由嵌套在UserDetail内,因为我不想在同一个页面里面再加载UserDetail
{path:':id',component: UserDetail,beforeEnter:(to,from,next)=>{
next();
}},
{path:':id/edit',component: UserEdit,name:'userEdit'}
]}
}
- 要加载的那个组件UserDetail,到组件内
//UserDetail.vue
<script>
export default{
//与生命周期钩子类似,只不过是vue-router实现的
beforeRouterEnter(to,from,next){
//next之前,组件没有创建,不能访问组件内的data,自定义检验方法
if(true){
next(vm =>{
vm.link
})
}else{
next(false)
}
//访问实例,需要等创建完成,可给next函数传一个回调函数,把这个组建的实例,传入一个函数(这个函数在组件加载完成之后执行)
}
}
</script>
- 以及用户想要离开之前
//UserEdit.vue
<template>
...
<button @click="confirmed = true"></button>
</template>
<script>
export default{
data(){
return{
confirmed:false
}
}
beforeRouterLeave(to,from,next){
//next之前,组件没有创建,不能访问组件内的data,自定义检验方法
if(this.confirmed){
next()
}else{
if(confirm('Are you sure?')){
next();
}else{
next(false);
}
}
//访问实例,需要等创建完成,可给next函数传一个回调函数,把这个组建的实例,传入一个函数(这个函数在组件加载完成之后执行)
}
}
</script>
路由懒加载
//routes.js
//延迟加载
const User = resolve =>{
require.ensure(['./components/user/User.vue'],()={
resolve(require('./components/user/User.vue'));
},'user');
}
//几个打包为一个子包,可以通过传入一个参数把他们组合在一起,第三个参数,分组名
import User from './components/user/User.vue';
export const routes = {
//若路径为空,则是首先进行加载的根路由
{path:'',component: Home}
{path:'/user',component: User}
}