【VUE】路由 vue-router

1,881 阅读13分钟

路由 vue-router

如何用vue来创建单页应用,路由在单页应用中非常重要

单页应用,技术层面上,只有唯一的一个页面,也就是在index.html里面,会存放整个vue应用,然后使用这个唯一的页面,模拟出可以跳转页面的交互感觉,所以url还是会变化的,而且我们会监听这些url的变化,然会对这个唯一的页面进行重载,所以感觉上,像是在浏览不同的页,但是在后台,始终都只有这一个页面。我们不再需要在应用里,加载从服务器获取到的各个页面,然后用Vue对页面的某一部分进行处理。现在整个应用都是用Vue来控制的,可以用Vue来创建更加复杂和庞大的应用。

使用路由在不同组件之间进行切换,以及动态地对各个部分进行加载

可以把组件想象成普通的可以切换的页面

vue的核心应用场景是应用于多页应用中,用来添加各种插件,或者添加部分内容

  1. 项目配置

    • 在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'
      })
      

      history

  2. 路由链接导航

    ​ 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>
  1. 定义活动链接

    更改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>
    
  2. 通过代码导航(强制导航)

    用JavaScript实现跳转

    //User.vue
    <script>
    	export default{
            methods:{
                navigateToHome(){
                    //调用push添加跳转,因为我们要把这个路由添加到历史记录中,以便让浏览器的前进后退功能可以正常使用,push用法,传入要跳转的路由,也可以传入一个对象
                    this.$router.push('/');
                }
            }
        }
    
    </script>
    
    
  3. 配置路由参数

    要在路径里面传入动态的片段,只需要在路由里面加上一个冒号,,然后就是动态内容的名称,这里就是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)
})

使用守卫来保护路由

控制用户能否访问一个路由,或者能否离开

检测:

  • 在用户进入一个新的路由之前,

  • 以及用户想要离开之前

    1. 在用户进入一个新的路由之前,

      使用”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>
  1. 以及用户想要离开之前
//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}
}