在以前,前后端是不分离的,这个阶段通常是由服务端渲染好页面(SSR),再发送页面给前端去展示;接着到了前后端分离的阶段,前端向静态资源服务器拿资源,再通过 js 渲染页面,此时仍然是一个 url 对应一份 html+css+js。再后来出现了 SPA 单页面应用的概念,实际上它只有一个页面,但给我们的体验是多页面之间的切换。
SPA 是基于路由和组件的,其中路由可以看作是它的一个路径管理器,路由和组件之间互相映射,路由的切换就是组件的切换。Vue 的前端路由也就是 vue-router。
vue-router 提供了 hash 和 history 两种模式。
onhashchange
事件;window.history
的 API,url 的改变同样不会触发页面刷新,并且由于历史记录是在 history 栈压入或弹出的,这使得我们可以来回切换。实例化 vue-router 时会传入一个对象,可以给对象一个 option
,如 mode:'history'
,从而决定 vue-router 使用哪种模式。
在安装 vue-cli 的时候可以顺便安装 vue-router,或者之后我们通过 npm install
的方式手动安装。
如果是通过脚手架安装 vue-router,src
下会多出一个 router
文件夹,里面的 index.js
帮我们生成了配置的基础结构。
index.js
大致是这样的:
// 导入要使用到的组件
import VueRouter from 'vue-router';
import Vue from 'vue';
import Home from '../components/Home.vue';
import About from '../components/About.vue';
Vue.use(VueRouter); // 安装 vue-router
const routers = [ // 配置路由和组件的映射关系
{
path:'/',
redirect: '/home'
}
{
path:'/home',
components:Home
},
{
path:'/about',
components:About
}
]
const router = new VueRouter({ // 实例化 VueRouter 对象
routers
})
export default router; // 导出 router 对象
同时,main.js
文件中,导入 router 并在 Vue 实例下挂载:
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: function (h) { return h(App) }
}).$mount('#app')
接下来通过 <router-link>
和 <router-view>
使用路由:
// App.vue
<div id="app">
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<router-view></router-view>
</div>
假设 home
路由下嵌套着两个子路由 news
和 message
,即 /home/news
和 /home/message
,那么首先创建两个对应的组件,之后在 index.js
下配置好映射关系:
import HomeNews from '../components/HomeNews.vue'
import HomeMessage from '../components/HomeMessage.vue'
const routes = [ // 配置 url和组件的映射关系
{
path:'/',
redirect: '/home'
}
{
path:'/home',
component:Home,
children:[
{
path:'/', // 默认展示 news
redirect: 'news'
}
{
path: 'news',
component: HomeNews
},
{
path: 'message',
component: HomeMessage
}
]
},
{
path:'/about',
component:About
}
]
在 Home.vue
中使用子路由:
// Home.vue
<div>
<h1>我是Home</h1>
<router-link to="/home/news">news</router-link>
<router-link to="/home/messaage">message</router-link>
<router-view></router-view>
</div>
有的时候,path 并不是固定的。比方 /user/Tom
,/user/Jack
,这些 path 根据用户不同而不同,但都是展示用户页面的,我们希望满足这种格式的 path 都映射 User
组件,怎么办呢?可以在 path 中使用动态路径参数。
import HomeMessage from '../components/User.vue'
const routes = {
{
path:'/user/:userId',
component: User
}
}
注意,这里我们不再是写 /user/
,而是写 /user/:userId
,这是因为路径 user 后面的东西是动态的。那么,对于下面的三种 <router-link>
,最后都可以成功映射到 User
组件:
// App.vue
<router-link to="/user/Tom"></router-link> // 路径:/user/Tom
<router-link to="/user/Jack"></router-link> // 路径:/user/Jack
<router-link :to="`/user/${userId}`"></router-link> // 路径:/user/Bake
<router-view></router-view>
export default{
data(){
return {
userId:'Bake'
}
}
}
如果想要在具体的 User.vue
中展示用户名,可以通过 $route.params
去访问:
// User.vue
<div>我的用户名是:{{$route.params.userId}}</div>
<div>我的用户名是:{{id}}</div>
export default{
computed:{
id(){
return this.$route.params.userId;
}
}
}
实际上,上面讲的动态路由就可以用来传递参数。上面例子的 path
还可以根据需要添加更多动态路径参数,如 '/user/:userId/:userJob/:userEmail'
,首先在 App.vue 拿到数据,传给<router-link>
的 to
,接着就可以在 User.vue 中通过 $route.params
去访问了。
to
传入对象首先要明白,to
除了接收字符串之外,也可以接受对象。以前面的动态路由为例:
<!--可以这样写(直接传字符串)-->
<router-link :to="`/user/${userId}`"></router-link>
<!--也可以这样写(传对象,直接用字符串表示完整路径)-->
<router-link :to="{path:'`/user/${userId}`'}"></router-link>
那么,我们会自然想到,对于一个普通的路由,要传参的话把参数放在给 to
传入的对象中不就可以了吗?
于是可能会这么写:
<router-link :to="{path:'/article',params:{date:2020-1-1,title:'A new year'}}"></router-link>
然而这是错误的用法,事实上我们应该将 path
改为 name
。不过在这之前,我们还是先给路由一个 name
吧:
import Article from '../components/Article.vue'
// 首先给路由一个名字(命名路由)
const routes = {
{
path:'/article',
name:'article',
component: Article
}
}
接着使用路由:
<router-link :to="{name:'article',params:{date:2020-1-1,title:'A new year'}}"></router-link>
获取参数:
<!--Article.vue-->
<div>{{$route.params.date}}</div>
<div>{{$route.params.title}}</div>
query
这和上面的是差不多的,不同的是我们不使用 params
,而是使用 query
。query 实际上就是 url 中的查询参数。
这种情况下,我们给 to
传入的对象可以使用 path
,也可以使用 name
:
<router-link :to="{name:'article',query:{date:2020-1-1,title:'A new year'}}"></router-link>
<!--等价于-->
<router-link :to="{path:'/article',query:{date:2020-1-1,title:'A new year'}}"></router-link>
获取参数:
<!--Article.vue-->
<div>{{$route.query.date}}</div>
<div>{{$route.query.title}}</div>
前面介绍的路由跳转/导航是通过声明式的 <router-link :to='...'>
实现的,我们也可以使用编程式的 this.$router.push('...')
实现:
// App.vue
<div id="app">
<router-link to='/home'>可以点击这里进入首页</router-link>
<!--
or: <router-link :to="{path:'/home'}">
-->
<button @click="change">也可以点击这里进入首页</button>
<router-view></router-view>
</div>
<script>
export default{
name:'App',
methods:{
change(){
this.$router.push('/home');
//or: this.$router.push({path:'/home'})
}
}
}
</script>
<router-link :to='...'>
实质上也是在内部调用了 push
方法,从而向 history 栈压入新记录,由于是栈的数据结构,所以可以自由前进和后退。除了 push
,还有 replace
(注意是直接替换而不是采用入栈出栈方式),go
,这些和 window.history
的 API 是类似的。
this.$router.push('...')
同样接受字符串参数或者对象参数。
$router
和 $route
的区别$router
是我们 new 出来的 VueRouter 实例,它提供了一些跳转方法( push
, replace
,go
)和钩子函数(后面导航守卫部分会讲解);$route
是路由信息对象,可以理解为是当前活跃的路由,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。路由的导航守卫其实就是一些钩子函数,可以在路由跳转的流程中针对性地进行操作控制。
router.beforeEach((to,from,next) => {...})
。可以在 index.js
或者路由组件中使用(通过 this.$router
),next 必须调用。router.beforeResolve
。router.afterEach((to,from) => {...})
。可以在 index.js
或者路由组件中使用(通过 this.$router
),next 不需要调用。单个路由独享的守卫只有 beforeEnter
这一个,可以在配置路由时定义。
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
beforeEnter: (to, from, next) => {
// ...
next()
}
}
]
})
组件守卫只能在路由组件中定义:
beforeRouteEnter((to,from,next) => {...})
: 进入路由前,此时实例还没创建,无法获取到 thisbeforeRouteUpdate((to,from,next) => {...})
:路由复用同一个组件时触发,比如 /user/Tom
和 /user/Jack
beforeRouteLeave((to,from,next) => {...})
: 离开当前路由,此时可以用来保存数据,或数据初始化,或关闭定时器等等懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。在单页应用中,如果没有应用懒加载,运用webpack 打包后的文件将会异常的大,导致进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
要使用路由懒加载,只需要替换掉原来的引用语句即可:
// import Home from '../components/Home.vue';
// import About from '../components/About.vue';
const Home = () => import('../components/Home');
const About = () => import('../components/About');
路由跳转的时候,比如 home -> about -> home
,home
路由组件实际上是在不断地创建和销毁,我们可以用生命周期钩子函数证明这一点:
上图中,每次跳转的时候都会经历一次生命周期。但大部分时候,这种重新渲染是没有必要的,所以 Vue 提供了一个内置组件 keep-alive
来 缓存组件内部状态,避免重新渲染 。
<keep-alive>
<router-view></router-view>
</keep-alive>
被 keep-alive
包裹的路由/组件,状态会得到缓存。以上图为例,从 home 跳转到 about,home 不会被销毁,同样的,从 about 跳转到 home,about 不会被销毁,home 也不会被重新创建,而是用之前缓存好的组件。
activated
和 deactivated
两个钩子函数(在路由组件中定义),前者在当前路由组件激活时调用,后者在当前路由组件失活时调用。include
包含的组件(可以为字符串,数组,以及正则表达式,只有匹配的组件会被缓存)exclude
排除的组件(以为字符串,数组,以及正则表达式,任何匹配的组件都不会被缓存)max
缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数)参考: