路由有一个非常重要的概念叫路由表,路由表本质上就是一个映射表,决定了数据包的指向,可以进行路由和转发。
前后端分离:
后端只负责提供数据,不负责任何界面的内容。通常情况下,我们会把前端打包后的资源放置到一个静态资源服务器上,如Nginx。然后还会有一个后端服务器为客户端提供API接口。
我们只需要使用浏览器访问静态资源服务器,然后从静态服务器上拉取到HTML+CSS+JS,浏览器再去执行js代码,一般会去访问后端服务器提供的API接口,返回数据给客户端(浏览器)后再进行页面数据的渲染。
前端渲染,后端渲染、前端路由、后端路由:
SPA页面(单页面富应用)
,在前后端分离阶段中,增加了一层前端路由,html只有一个,通过不同的路由,将不同的组件展示到一个html中,整个应用中只有一个html页面。通过映射关系实现。前端来管理URL和页面之间的映射关系。url的hash和HTML5的history
既然说到了前端路由,当我浏览器更改url时,如何不让浏览器再去访问静态资源服务器呢,可以使用url的hash或html5的history。
url的hash:url的hash也就是锚点(#),本质上是改变window.location.href的属性。可以通过location.hash改变href,但是页面不会发生刷新,且url中多了一个#
锚点
html5的history:通过history的pushState也可以做到
// 入栈,入栈后浏览器会记录历史,可操作上一步,下一步
history.pushState({},'','home')
// 返回
history.back()
// 替换,不会记录历史
history.replaceState({}, '', 'demo')
首先安装 vue-router
npm install vue-router --save
然后再模块化工程中使用它,由于vue-router是一个插件,所以可以通过Vue.use()
来使用
创建组件 Home.vue
<template>
<div>
<h2>首页</h2>
</div>
</template>
<script>
export default {
name: "Home"
}
</script>
<style>
</style>
创建组件 About.vue
<template>
<div>
<h2>关于</h2>
</div>
</template>
<script>
export default {
name: "About"
}
</script>
<style>
</style>
在src目录中有一个router
的目录,然后创建一个index.js
文件,内容如下:
// 配置路由相关的信息
import Vue from 'vue'
import VueRouter from 'vue-router'
// 导入组件
import Home from '../components/Home'
import About from '../components/About'
// 1.通过Vue.use(VueRouter),使用这个插件
Vue.use(VueRouter)
// 2.创建路由对象
const routes = [
{
path: "/home",
component: Home
},
{
path: "/about",
component: About
}
]
const router = new VueRouter({
routes
});
//3.将router传给vue实例
export default router
在src目录中的main.js入口文件中,创建vue实例并且使用路由对象。
import Vue from 'vue'
import App from './App'
import router from './router' // 当from文件夹时,它会自动找文件夹下的index.js入口文件
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
render: h => h(App)
})
在App.vue入口组件中使用这些
router-link和router-view配合使用
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
const routes = [
{
// 匹配到 / 时(默认值),直接重定向到 /home路由
path: "/",
redirect: '/home'
}
]
创建vue-router实例时,指定mode为history
const router = new VueRouter({
routes,
mode: 'history'
});
a标签
,可以根据tag属性更改为其他标签,如div,button<router-link to="/home" tag="button">首页router-link>
replace ,增加此属性后,跳页面底层用的是 replaceState(),不会存在历史记录,不能点左右箭头
active-class : 当router-link处于激活状态时(被点击),增加一个类属性(不写时默认为 router-link-active
)
<router-link to="/home" tag="button" active-class="active">首页router-link>
<router-link to="/about" active-class="active">关于router-link>
<style>
.active{
color: red;
}
style>
实现后,点击哪个 router-link,哪个router-link中的文本就会变成红色的
但是假如有多个 router-link,难道一个一个加 active-class属性吗,当然不用。我们可以在实例化路由时进行全局化配置:
const router = new VueRouter({
routes,
mode: 'history',
linkActiveClass: 'active'
});
当我们不使用 router-link进行路由跳转时,还可以通过代码进行路由跳转。
<template>
<div id="app">
<!-- <router-link to="/home" tag="button" >首页</router-link>
<router-link to="/about" >关于</router-link> -->
<button @click="homeClick">首页</button>
<button @click="aboutClick">关于</button>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
homeClick(){
// 从 data()中默认返回的一个 $router对象
this.$router.push('/home') // 有历史记录
//this.$router.replace('/home') // 直接替换,无历史记录
console.log('homeClick')
},
aboutClick(){
this.$router.push('/about')
console.log('aboutClick')
}
},
// 注意,在vue中,所有的vue组件都会默认创建一个$router的属性,这里不需要写,是默认的,只是为了说明 $router是从哪里来的
data(){
return {
$router : ...
}
}
}
</script>
那么这种方式如果连续点击几次后发现报错了,那么可以给push或replace函数加一个异常捕获,我们直接把异常给吃掉。
this.$router.push('/home').catch(err => {})
// 或
this.$router.replace('/home').catch(err => {})
如访问到某一个用户id为2的详细信息,路径就是变成 /user/2
配置路由时,在path上面加传值匹配,匹配路由时 /user/xxx才会被匹配到,否则无法匹配到就找不到路由
const routes = [
......
{
path: "/user/:userId",
component: User
}
]
使用时(App.vue文件中),将具体的值拼接在path上面即可
用户
但是实际使用时那个id是需要从后端服务器动态获取来的,假如这么一个userId变量为2
export default {
name: 'App',
data() {
return {
userId: 2
}
}
}
那么如何拼接上这个变量呢?需要给router-link绑定上to属性
<router-link :to="'/user/' + userId">用户</router-link>
<router-view/>
实际项目中,我们需要从url中拿到我们path上面拼接的userId,并且在组件中做一个展示,那么我们如何做呢?
User.vue
<template>
<div>
<h2>用户界面</h2>
<h2>{{userId}}</h2>
<h2>{{$route.params.userId}}</h2> <!-- 和{{userId}}一样的效果 -->
</div>
</template>
<script>
export default {
name: "User",
// 我们可以创建一个计算属性,里面去 $route中获取参数列表中的 userId变量(path中拼接的那个值),参数列 表中的 变量名都是在 路由配置时定义的
computed:{
userId(){
return this.$route.params.userId;
}
}
}
</script>
那么前面我们学到的 $router
,这里又来了一个 $route
,这两个是什么意思呢?
$router:是获取到的我们创建的 vue-router的实例
const router = new VueRouter({
routes,
mode: 'history'
});
r o u t e : 是 我 们 获 取 到 的 处 于 活 跃 状 态 的 一 个 路 由 , 比 如 在 A p p . v u e 中 , 显 示 的 是 U s e r 组 件 , 那 么 这 个 route:是我们获取到的处于活跃状态的一个路由,比如在App.vue中,显示的是 User组件,那么这个 route:是我们获取到的处于活跃状态的一个路由,比如在App.vue中,显示的是User组件,那么这个route就是获取的 User组件相关的路由。
const routes = [
......
// User组件的相关路由
{
path: "/user/:userId", //:userId,是在参数列表中定义了一个 userId的变量,组件中可以取出来
component: User
}
]
我们先执行 npm run build
打包一下项目,然后打开dist目录下的js目录,会发现:
当打包构建应用时,JavaScript包会变得非常大,影响页面的加载。如果我们能把不同路由对象的组件分割成不同的代码块,然后当路由被访问的时候才加载对应的组件,这样会很高效。
由上面打包解析可知,为我们模块导入导出做支撑的代码和第三方插件代码一定要先加载,不然会导致功能不可用。那么我们可以懒加载入手的就是我们的业务层代码
,需要用到时再加载某个业务代码。
如何使用?
在配置路由的时候,不直接配置组件,而是配置一个箭头函数,然后导入相关的组件,这样子就能够每一个路由生成一个js文件,当浏览器访问时,不会把所有的资源js文件都下载到本地,而是用到某一个路由的时候,再把相应的路由js文件下载到浏览器执行。
方式一:AMD写法
const About = resolve => require(['../components/Abount.vue'],resolve)
方式二:ES6写法(推荐)
const Home = () => import('../components/Home.vue')
详细router下的index.js配置
// 配置路由相关的信息
import Vue from 'vue'
import VueRouter from 'vue-router'
// 路由懒加载方式
const Home = () => import('../components/Home')
const About = () => import('../components/About')
const User = () => import('../components/User')
Vue.use(VueRouter)
// 2.创建路由对象
const routes = [
{
path: "/",
redirect: '/home'
},
{
path: "/home",
component: Home
},
{
path: "/about",
component: About
},
{
path: "/user/:userId",
component: User
}
]
const router = new VueRouter({
routes,
mode: 'history'
});
//3.将router传给vue实例
export default router
然后执行打包npm run build
,打包后的js目录中,可以看到一个懒加载路由对应一个js文件。
假如Home主页组件中又有两个子组件要分别展示,那么就需要用到嵌套路由。实现嵌套路由有两个步骤:
定义两个组件
HomeNews.vue
<template>
<div>
<ul>
<li>新闻1</li>
<li>新闻2</li>
<li>新闻3</li>
</ul>
</div>
</template>
<script>
export default {
name: "HomeNews"
}
</script>
HomeMessage.vue
<template>
<div>
<ul>
<li>消息1</li>
<li>消息2</li>
<li>消息3</li>
</ul>
</div>
</template>
<script>
export default {
name: "HomeMessage"
}
</script>
在Home路由中配置子路由,HomeNews和HomeMessage
// 采用懒加载路由方式
const Home = () => import('../components/Home')
const HomeNews = () => import('../components/HomeNews')
const HomeMessage = () => import('../components/HomeMessage')
const routes = [
{
path: "/",
redirect: '/home'
},
{
path: "/home",
component: Home,
children:[ // 在Home路由下,配置子路由
{
path: 'news', // 子路由中的path 不能加 /,直接定义path就行
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
]
在Home.vue中使用标签,展示子组件
<template>
<div>
<h2>首页</h2>
<!-- 这里 to路径一定要写全,否则找不到 -->
<router-link to="/home/news">新闻</router-link>
<router-link to="/home/message">消息</router-link>
<router-view></router-view>
</div>
</template>
同样可以在子组件中定义一个默认路由:
children:[
{
// path直接为空即可
path: '',
// 重定向到 news
redirect: 'news'
},
{
path: 'news',
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
传递参数主要有两种类型:params和query
配置动态路由的时候,在path上拼接对应的值,然后在组件中可以this.$route.params.xxx
获取到这个值。
普通方式配置路由,传递时使用对象的方式来进行传值,路径上会变成问号传值的方式把数据展示在url上面。
比如配置一个Profile.vue组件:
<template>
<div>
<h2>我是Profile组件</h2>
</div>
</template>
<script>
export default {
name: "Profile"
}
</script>
配置路由:
{
path: "/profile",
component: Profile
}
具体传值方式:直接绑定 to属性,给传入一个对象。path就是跳转路径,query中写的是拼接的k-v
档案
在Profile.vue中如何取出这些数据呢?
使用 $route.query可以获取到一个对象,$route.query.xxx获取到具体的属性的值
<template>
<div>
<h2>我是Profile组件</h2>
<h2>{{$route.query.name}}</h2>
<h2>{{$route.query.age}}</h2>
</div>
</template>
如果不使用router-link,使用代码方式呢?
$router.push方式进行跳转,push中可以直接传递一个对象。
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userId: 2
}
},
methods: {
userClick(){
this.$router.push('/user/'+ this.userId)
},
profileClick(){
this.$router.push({
path: "/profile",
query:{
name: 'wlh',
age: 21
}
})
}
}
}
</script>
监听从某一个组件跳到另一个组件时,我们可以做些事情。比如,从一个组件跳到另一个组件时,把当前的title标题改一下。
再来复习下vue的生命周期
那么在vue-router中如何全局监听路由的跳转呢?
前置钩子,或者叫前置守卫。
router实例中有一个 beforeEach()的方法,需要在此方法中进行操作。我们可以在路由配置时给每个route对象加上一些元数据
const routes = [
...
{
path: "/about",
component: About,
meta:{
title:"关于"
}
},
{
path: "/user/:userId",
component: User,
meta:{
title:"用户"
}
},
{
path: "/profile",
component: Profile,
meta:{
title:"档案"
}
}
]
const router = new VueRouter({
routes,
mode: 'history'
});
// 使用 router的beforeEach方法 (前置钩子函数)
router.beforeEach((to, from, next) => {
// 从 to中(要跳转到的route中) 找到元数据,给当前title赋值
document.title = to.matched[0].meta.title;
// 放行,注意如果不放行,路由就不会跳转了
next()
});
to(也就是route对象)中有个matched的列表,这里存放的是所有有关联的route,一般是父子路由信息展示为一个列表,只找第一个(父路由)即可。
既然有前置钩子,那么同样有后置钩子,感觉跟spring的拦截器很像,方法执行前拦截和方法执行后拦截。。
如果是后置钩子afterEach函数,不需要调用next()函数,因为next()已经执行过了
router.afterEach((to, from) => {
console.log('--------------')
});
守卫中有个next()函数,这个函数有几种传参方式:
那么上面说的都是全局守卫,能否搞一些局部守卫呢?当然可以
路由独享守卫, beforeEnter函数
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
}
},
]
beforeEnter
守卫 只在进入路由时触发,不会在 params
、query
或 hash
改变时触发。例如,从 /users/2
进入到 /users/3
或者从 /users/2#info
进入到 /users/2#projects
。它们只有在 从一个不同的 路由导航时,才会被触发。
组件内守卫(见名知意,是在组件中创建的)
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
不过,我们可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
实际上,我们前面的路由跳转,每次跳转时,上一次的路由的组件都会被销毁,跳转到一个组件时再创建这个需要跳转到的vue组件。但是实际项目中可能存在需求:要保留我上次组件状态
使用keep-alive标签后,里面的路由跳转时都不会被销毁,而是保存在内存中。
keep-alive使用后,只有被包含在keep-alive中的组件,可以使用 activated()和deactivated(),来监听当前组件是否是活跃状态.需要注意的是:这个被保存的组件如果下面有子组件,那么子组件是不会被保存起来的
App.vue中,使用keep-alive,也就是里面的所有组件都只被创建一次后会被保存到内存中。
<template>
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<!-- <router-link :to="'/user/' + userId">用户</router-link>
<router-link :to="{path:'/profile', query: {name:'wlh', age:21}}">档案</router-link> -->
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
在Home.vue子组件中:
<template>
<div>
<h2>首页</h2>
<router-link to="/home/news">新闻</router-link>
<router-link to="/home/message">消息</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Home",
data(){
return {
path: '/home/news'
}
},
activated() {
this.$router.push(this.path)
},
// 在导航离开渲染该组件的对应路由时调用
beforeRouteLeave (to, from, next) {
// this.path = from.path
this.path = this.$route.path;
next()
}
}
</script>
其实keep-alive中,有两个很重要的属性
<keep-alive exclude="Profile,User">
<router-view/>
</keep-alive>