router

文章目录

路由

路由 ( routing ) 是网络工程里的术语,它通过互联的网络把信息从源地址传输到目的地址的活动。(摘自维基百科)

简单举例

生活中最常见的有关路由的东西是路由器,它提供了两种机制:路由和转送

  • 路由时决定数据包从来源到目的地的路径

  • 转送将输入端的数据转移到合适的输出端。

路由中有一个非常重要的概念叫路由表

  • 它本质上就是一个映射表,决定了数据包的指向

当我们从运营商那边拉了一条网线之后(给个猫,猫上连出一条网线),我们虽然可以直接插到电脑上,但更多的是把它插到路由器上,再从路由器上连出来一堆网线,或者无线wifi,我们连接这个,这时候我们电脑就会被分配到一个内网 IP,像这样 192.168.0.100这种 192 开头的地址。在内网内我们都使用这个通信,我们的公网 IP 在路由器上,路由器上有一个映射表,它类似于这种结构。如果在外网有一个人向你的 QQ 发送了一条信息,它会先经过层层路由,再发送到你的公网,也就是你的路由上,再由你的路由根据这个映射表,找到你这台电脑,并把数据传到你的电脑上。同样的,发送信息就是接收信息的反过程。

1
2
3
4
5
6
7
8
[
{
"内网IP1": "机器 1 的 MAC 码,这是标识,它是唯一的"
},
{
"内网IP2": "机器 2 的 MAC 码,这是标识,它是唯一的"
}
]

前后端路由发展

后端渲染

最开始只有后端渲染,也因此只有后端路由,那时候前端没有 js,只负责 html + css,相当于美工。页面的渲染交给后端来实现,他们可能会使用 JSP (java server page)、php、asp、net 这些脚本语言来直接获取本地服务器里的数据,并且直接渲染到页面上,并生成一个 html + css 文件。这里面已经把内容都加好了,是个静态网页,被放在服务端,并建立一个映射表,当用户发送 url 时,找到对应的 html + css 返回给用户。 结构可能是这样的,因此叫做后端路由,这个阶段也叫后端渲染,后端基本负责了整个网站,并且 JSP 这些语言与 html、css 混杂在一起,非常难以维护,而前端由于不懂 JSP 这些语言,也无法维护网站,全靠后端。

1
2
3
4
5
6
7
8
[
{
"url1": "写死的(页面内容已经写好的) html1 + css1"
},
{
"url2": "写死的(页面内容已经写好的) html2 + css2"
}
]

这种方式的好处是数据渲染在后端完成,客户端不需要处理什么,只需要解析 html,以及利于 seo 优化。

前后端分离

接着出现了 ajax,直接让前后端分离了。此时的情况是 后端可能会有两台服务器(也可能是一台,同时提供静态资源与 API 接口),一台专门提供 API 接口,一台专门提供静态资源,当用户在发送 url 时,会像静态服务器请求资源,拿到 html + css + js,之后再根据 js 代码,通过 ajax 向 API 服务器请求数据,得到数据后返回到用户端,用户端的 js 代码再根据这些数据,操作 DOM 元素来渲染页面内容。

此时就完成了前后端的分离,前端专注交互与页面渲染,后端专注数据与请求。此时的结构可能是这样的

1
2
3
4
5
6
7
8
[
{
"url1": "空的 html1 + css1 + js1 (得到 js 后再由它发送请求到 API 服务器,获取数据并渲染到 html)"
},
{
"url2": "空的 html2 + css2 + js2 (得到 js 后再由它发送请求到 API 服务器,获取数据并渲染到 html)"
}
]

可以看到,此时仍然是后端路由,但实现了前后端分离。较上一种的好处是实现了局部渲染,以及利于 seo 优化。

SPA(single page application,单页面应用)

整个网页只有一个页面,用了某种技术(hash 路由 或 h5 的history 模式),使得用户在多次改变 url 的时候,不会再向服务器发送多次请求了。此时的情况是,后端仍然是两台服务器不变,当用户输入了 url,静态资源服务器直接返回完整的、全部页面的 html + css + js,也是该服务器里这个域名对应的唯一的 html + css + js 代码(不会再像之前一样,每个 url 都获取各自不同的 html + css + js,即使是同个域名)。 此时的 url 由前端 JS 掌握,一旦 url 发生改变,先去 JS 代码中找到是否有匹配的 html + css + js(其实这是一个组件),如果有,就直接渲染出来。(如果没有要么报错要么跳转)。此时的结构可能是这样的

1
2
3
4
5
6
7
8
[
{
"url1": "组件1(该页面的 html1 + css1 + js1)"
},
{
"url2": "组件2(该页面的 html2 + css2 + js2)"
}
]

这就是前端路由,根据 url 的不同,由 JS 判断,返回不同的组件,并根据组件里的 JS 内容,发送不同的请求到 API 接口获取数据,并渲染到页面上。

这种渲染方式(客户端渲染)的弊端在于 seo 优化,爬虫无法爬取到真正的网页内容(直接右键查看网页源代码,无法查看到真正内容,只能看到一个未被替换的 <div id="app" /> ),以及首屏加载速度(由于需要直接下载一堆的 css + js)的问题。

SSR(server side render,服务端渲染)

为了解决客户端渲染首屏加载速度慢与 SEO 问题,ssr 解决方法出现。服务端渲染出 首屏 (不是首页,而是第一次 url 请求的页面)的 DOM 结构返回,前端拿到内容带上首屏,后续的页面操作,再用单页路由和渲染。实际上仍然是个单页面应用。流程是 客户端发送 url 请求到服务端,服务端读取对应的 url 模板信息,并在服务端做出 html 和数据的渲染,渲染完成之后返回 html 结构,客户端这时拿到了首屏的 html 结构,而不是一个等待渲染的 <div id="app" />,因此查看网页源代码是可以直接看到 DOM 结构的,并且由于直接拿到了首屏内容,浏览首屏的速度会非常快。

SSR 是客户端渲染与后端渲染的一个折中的方案,在渲染首屏的时候在服务端做出了渲染,注意仅仅是首屏,其他页面仍然需要在客户端渲染,服务端接收到请求之后会渲染出首屏页面,并且携带着剩余的路由信息,预留给客户端去渲染其他路由的页面。

Vue 的 SSR 可以使用 Nuxt.js,不过这不是本文的重点。由于爬虫爬取的页面是一个一个页面爬取的,也就是说,每次拿到的都是首屏,所以完成了 SEO 优化。

hash 路由 与 h5 的 history 模式

目前主流的三大框架都有自己的路由实现

  • Angular 的 ngRouter
  • React 的 ReactRouter
  • Vue 的 vue-router

当然这里的重点是 vue-router

hash 路由 (默认模式)

location.hash = 'foo' ,地址就会变成 域名/#/foo 这将不会发送请求,通过前端路由决定加载的 html css 内容

location.href = 'bar' ,地址就会变成 域名/bar#/ 这将会发送请求,向服务器获取 html css 等资源

hash 模式下,地址栏最后会默认加上 #/

所以实际上是监听 hash 的改变来决定网页的内容

h5 的 history 对象

history.pushState({}, '', home),地址就会变成 域名/home 这也不会发送请求,通过前端路由决定加载的 html css 内容 , home 入栈

history.pushState({}, '', about),地址就会变成 域名/about 不会发送请求, about 入栈

history.pushState({}, '', about),地址就会变成 域名/about 不会发送请求, about 入栈

history.back() 地址就会变成 域名/home 不会发送请求, about 出栈(如果在 hash 模式下使用这个方法,地址就会变成 域名/home#/ )

history.replaceState({}, '', user),地址就会变成 域名/user 不会发送请求,但会清空栈的所有内容,再把 user 入栈,栈中只有 user 一个内容

history.go(-1) 等价于 history.back() 在栈中推出一位, go(-2) 就是出两位

history.go(1) 等价于 history.forward() 在栈中加入一位,go(2) 就是入两位

history.pushState(state, title, url) 、 history.replaceState(state, title, url),两者参数相同

  • state: 可以通过 history.state 读取,这个参数是对象

  • title: 可选参数,暂时没有用,建议传个空字符串,当前大多数浏览器都会忽略这个参数。

  • url: 改变后的地址

vue-router

基础

vue-router 有点像中间件,在路由跳转的中间需要经过它的控制。

安装 npm i vue-router -S

使用,新建一个 router 文件夹,下面新建一个 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* /router/index.js */
import Vue from 'vue'
import VueRouter from 'vue-router'

// Vue.use 实际上是调用了传入对象的 install 方法,相当于 VueRouter.install(),因此插件必须有 install 方法才能使用 Vue.use ,就是安装插件的意思
Vue.use(VueRouter)

import Home from '../components/Home.vue'
import About from '../components/About.vue'
import User from '../components/User.vue'

const routes = [
// 注意,这种特殊的一般要养成习惯写在最前面方便查看,实际上它放到后面效果也是一样的,但写在第一位方便查看
{
path: '', /* 写 / 和不写 / 效果一样 */
redirect: '/home' // 重定向
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/user/:userId', // 动态路由
component: User
}
]

const router = new Router({
routes,
mode: 'history', // 默认使用 hash 路由,会在后面加上 #/,如果不喜欢,可以这样修改成 h5 history
linkActiveClass: 'active' // 用于修改全局的 router-link 的激活样式名,原来叫做 router-link-active,现在变成了 active

})

export default router

/* /main.js */
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 自动添加文件夹下的 index.js

new Vue({
router, // 需要注册 router
render: h => h(App)
}).$mount('#app')

App.vue 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div>
<!--
vue-router 内部给我们注册了两个全局组件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
-->

<router-link tag="button" to="/home" replace active-class="active">首页</router-link>
<router-link to="/about">关于</router-link>
<router-link :to="'/user/' + userId">关于</router-link> <!-- 动态路由 -->
<router-view></router-view> <!-- 路由占位符,到时候路由表映射的组件将会被替换到这里 -->
</div>
</template>

<script>
export default {
data() {
return {
userId: ''
}
}
}
</script>

<!--
router-link 的属性
tag: 默认是 a,即 router-link 会变成 a标签,修改成别的 DOM 标签,就会变成其他的 DOM 标签
replace: 是个布尔值,表示是否使用 replaceState 来更改路由,默认使用 push。如果使用,它会清空存放历史记录的栈,因此无法使用 history.back()、history.forward()、history.go()
active-class: 控制单个 router-link 的激活样式名,这里改成了 active,上面的文件里定义的是修改了全局的激活样式名。router-link 激活后默认会增加两个类名 router-link-active 和 router-link-exact-active. 上面修改的是 router-link-active。在设置底部或顶部导航高亮的时候可能会用到这个类名,一般也不会修改它的名字,大家都使用默认的激活名。
-->

<!--
路由跳转也可以用 js 实现,vue-router 为所有的V
this.$router.push('/home') 类似于 history.pushState(state, title, url)
this.$router.replace('/home') 类似于 history.replaceState({}, '', user)
虽然它们都与 h5 的方法类似,但不要使用 h5 的,不要绕过 router 对象来控制路由。
-->

动态路由

1
2
3
4
5
6
7
8
9
//  /router/index.js
// import 组件 from 'xxx' 这里就不写了,过于繁琐
const route = [
//...
{
path: "/user/:userId",
component: User
}
]

App.vue

1
2
3
4
5
6
<template>
<div>
<router-link :to="'/user/' + userId">关于</router-link> <!-- 动态路由 -->
<router-view></router-view> <!-- 路由占位符,到时候路由表映射的组件将会被替换到这里 -->
</div>
</template>

User.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
{{userId}} <!-- 两种写法效果一样 -->
{{$route.params.userId}}
</div>
</template>

<script>
export default {
name: 'User', // 写了这个之后可以在 vue-devtools 中输入 User 拿到这个组件
computed: {
userId() {
// params: 翻译为 parameters(参数)
// $route 表示被激活的路由,即在 router 对象中配置了那么多的路由对象,当前页面哪个被激活了,这个 $route 就指向哪个对应的路由对象
return this.$route.params.userId
}
}
}
</script>

懒加载

在 vue-cli3+ 中,我们打包后的 js 文件会默认变成 3 个,app.js、vendor.js、manifest.js(vue-cli4+ 中变成两个,这个 manifest 不见了),这就是分包,把一个很大的 js 代码拆分。vue-cli3 已经帮我们下好了分包插件。

  • app.js 表示我们自己写的业务代码

  • vendor.js 表示我们引入的第三方库或插件

  • manifest.js 表示代码的底层支撑,如需要用到的 CommonJS 等

当日后项目的代码量越来越大的时候,app.js 文件也会越来越大,到时候就用户页面会出现短暂空白的情况,因为一直在下载这个 js 文件。这时候我们的处理办法基本是一个路由(路由对应的组件)打包成一个 js 文件,把它们都分开,并且请求它们时不会被全部返回,而是按需向服务器请求,如果我们只点击了首页,则只返回首页的 js 文件,其他仍只存在于服务器上。

懒加载的三种写法
  • 方法一:结合 Vue 的异步组件和 Webpack 的代码分析,非常繁琐,现在基本只在老项目中可能见到

    const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) }) }

  • 方法二:AMD 写法

    const About = resolve => require(['../components/About.vue'], resolve)

  • 方法三:在 ES6 中,有更简单的写法来组织 Vue 异步组件和 Webpack 的代码分割,现在基本都是这种

    const Home = () => import('../components/Home.vue')

现在,按照上面的方法而完成的懒加载是每个路由对应一个 js 文件,如果我们想要分组打包,比如我们有 home 页面和 login 页面,并且这两个页面关系紧密(或者假设我们有两个页面,一个页面嵌套在另一个页面中,即嵌套路由),我们希望它们可以被一起加载,即被打包成一个 js 文件。我们可以使用 babel 插件 npm i @babel-plugin-syntax-dynamic-import,并在 babel.config.js 中引入它,之后我们就可以改写成

1
2
3
4
const Home = () => import(/* webpackChunkName: "Home_Login" */ '../components/Home')
const Login = () => import(/* webpackChunkName: "Home_Login" */ '../components/Login')

// 里面的注释内容会被解析,如果它们的 webpackChunkName 相同,它们最后会被打包到同一个 js 文件中,并且 js 文件名字就叫 Home_Login.[:hash].js,这个名字可以任意,只是想分到同一组的模块需要 webpackChunkName 相同。

嵌套路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//  /router/index.js
// import 组件 from 'xxx' 这里就不写了,过于繁琐
const route = [
//...
{
path: '', /* 写 / 和不写 / 效果一样 */
redirect: '/home' // 重定向
},
{
path: "/home",
component: Home,
children: [
{
path: '', // 子路由的重定向这里一定不能写 /
redirect: 'message' // 子路由的重定向
},
{
path: 'message', // 子路由不要带 / ,会自动加上的,虽然带 / 也能实现,但效果不一样
component: Message
},
{
path: 'news',
component: News
}
]
}
]

/*
正常情况下,子路由应该是 path: 'message',url 自动会变成 域名/home/message
如果设置 path: '/message', url 就会变成 域名/message ,相当于和父路由同级了,但仍会显示子路由的内容。
*/

Home.vue

1
2
3
4
5
6
7
8
<template>
<div>
<!-- to 的路径必须写全,不能写成 message 或 news -->
<router-link to="/home/message">message</router-link>
<router-link to="/home/news">news</router-link>
<router-view></router-view> <!-- 路由占位符,到时候路由表映射的组件将会被替换到这里 -->
</div>
</template>

路由传参

有两种方式,一种是上面提到的动态路由,通过 params 获取参数,一种是传入 query,通过 query 获取参数。

  • params 的类型:

    • 配置路由格式: /router/:id
    • 传递方式:在 path 后面跟上对应的值
    • 传递后形成的路径: /router/123, /router/xiaoming
    • 参数获取方式:this.$route.params.id
    • 额外:当然配置格式也可以写成:/router/:id/:age/:height 这种形式,就可以传入多个参数了,写起来就挺麻烦的,而且形成的路径也会多加好几个 /,所以传多个参数的时候一般不用这个方式。所以一般传一个参数的时候可以用用这种。
  • query 的类型

    • 配置路由格式:/router,也就是普通的配置
    • 传递的方式:对象中使用 query 的 key 作为传递方式
    • 传递后形成的路径: /router?id=123, /router?id=xiaoming
    • 参数获取方式:this.$route.query.id
    • 额外:传入多个参数的时候可以用这种,写起来比较方便,不过它直接把键名也暴露在 url 地址栏中了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!-- App.vue -->
<template>
<!-- 这两种写法效果完全一样, to 可以传入一个对象,默认我们传入的字符串会变成该对象 path 属性值-->
<router-link :to="{path: '/home'}"></router-link>
<router-link to="/home"></router-link>

<!-- params 传参 -->
<router-link :to="'/user/' + userId">用户</router-link>
<button @click="userClick"></button>

<!-- query 传参 -->
<router-link :to="{ path: '/profile', query: { name: 'dl', age: 20, weight: 123 }}">档案</router-link>
<button @click="profileClick">档案</button>

<router-view></router-view>
</template>

<script>
export default {
data() {
return {
userId: 'dl'
}
},
methods: {
userClick() {
this.$router.push('/user/' + this.userId)
},
profileClick() {
this.$router.push({
path: '/profile',
query: {
name: 'dl',
age: 20,
weight: 123
}
})
}
}
}
</script>

<!-- User.vue -->
<template>
<div>
{{$router.params.id}}
</div>
</template>


<!-- Profile.vue -->
<template>
<div>
{{$route.query.name}}
{{$route.query.age}}
{{$route.query.weight}}
</div>
</template>

顺便说一下 URL 的组成

URL: scheme://host:port/path?query

URL: 协议://主机:端口/路径?查询

this.$route.params 获取的就是当前的 paththis.$route.query 获取的就是当前的 query

port 网页默认是 80 端口,可以省略,但如果不是 80 端口,就需要主动在地址栏里指定了。

导航守卫

当我们在每次路由跳转的时候,都想顺带执行什么操作的时候,就需要用到导航守卫。

全局守卫

一般常用的就前置守卫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 默认写法
router.beforeEach((to, from, next) => next())

// 看看源码, ts 写的,要求传入一个 gurad 参数,而它是 NavigationGuard 形式的,:NavigationGuard 是泛型,这个名字是自定义的,后面的 :Function 是泛型,表示 beforeEach 的返回类型是一个函数, :XXXXX 这种就是泛型
beforeEach(guard: NavigationGuard): Function

/* 再看看 NavigationGuard,可以看到,通过 type,它把 NavigationGuard 定义成了一个类型
* <V extends Vue = Vue> 是泛型约束, V 表示对象中的值类型, = Vue 是泛型参数默认类型,这一整个是我们要传入的检测类型,比如下面的代码, <T> 内的值就是定义了 T,T 代表类型,T 将被传到后面去,检测 arg: T 和函数返回值 :T
interface Length {
length: number;
}

function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
接着看看参数。 to 和 from 都是 Route,就是我们在
new Router({
routes: [
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
})
这里定义的 routes 数组中的每一项,都是 Route 对象。
再看看 next,可以看到它也是个函数,它决定了 Promise 的状态,因此如果不调用 next,Promise 永远都是未完成状态(pending),无法进行下一步,路由不会跳转。 直接调用 next() 就是路由从 from 跳转到 to
next(false) 表示中断当前的导航,返回 from 的地址
next('/login') 就是 next({ path: '/login' })
*/
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: (to?: RawLocation | false | ((vm: V) => any) | void) => void
) => any

举例,通过导航守卫来控制页面标题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// /router/index.js
const routes = [
{
path: '' ,
redirect: '/home'
},
{
path: '/home',
component: Home,
meta: { // meta 表示元数据,元数据是描述数据的数据。个人理解就是自定义的数据没地方放,放在别的地方不会挂载到对象上,但又想用,就只能挂在这。
title: '首页'
}
},
{
path: '/about',
component: About,
meta: {
title: '关于'
},
children: [
{
path: 'child',
component: Child
}
]
}
]

router.beforeEach((to, from, next) => {
/*
document.title = to.meta.title
本来这样写就完成了根据路由来控制页面标题,但输入 localhost:8080 的时候,页面会跳转到 localhost:8080/home,并且标题是 undefined,如果直接输入 localhost:8080/home,就会显示正常
这是因为输入 localhost:8080 的时候,拿到的 Route 是
{
path: '' ,
redirect: '/home'
}
里面并没有 meta,因此得到 undefined
不过新版好像即使这样写,拿到的也是 Home 这个 Route,不会再拿到根路由的 Route 了,所以新版直接写成上面那种也行。
*/
// to.matched 是一个数组,里面的值是匹配的 Route,如果路径是 /about/child,会先 push About 这个 Route,再 push Child 这个 Route,也就是说,父级会显示在第 0 项。而且它不会把 '/' 这样的重定向路由匹配进去。
document.title = to.matched[0].meta.title
next()
})

另外的几种钩子不太常用,用到的时候查文档。

afterEach(hook: (to: Route, from: Route) => any): Function 后置钩子,可以看到,它叫做 hook 而不是 guard,因为它没有 next,也不需要 next

路由独享的守卫

beforeEnter 路由独享的守卫,如果存在全局守卫,先执行全局守卫再执行路由守卫,它的方法参数与全局守卫一致。

1
2
3
4
5
6
7
8
9
10
11
12
const router = new Router({
routes: [
{
path: '/home',
component: Home,
beforeEnter: (to, from, next) => { // 路由独享的守卫,如果存在全局守卫,先执行全局守卫再执行路由守卫,它的方法参数与全局守卫一致。
// ...
next()
}
}
]
})

组件内的守卫

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。(该内容来自官方文档)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能! 获取组件实例 this
// 因为当守卫执行前,该组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用,而这个钩子就会在这种情况下被调用
// 可以访问组件实例 this
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 this
}
}

知道有这些守卫就行,到时候要用直接翻文档。

keep-alive

keep-alive 是 Vue 内置的一个组件,可以使被包裹的组件保留状态,或避免被重新污染。举例,假设包了个 input 框,里面的值在非激活状态下也不会被抹去,下次重新激活的时候,这个值还在。

router-view 也是一个组件,如果被包在 keep-alive 里,所有路径匹配到的视图组件都会被缓存。

1
2
3
4
5
6
7
8
<template>
<!-- 写法 -->
<router-link to="/home">首页</router-link>
<router-link to="/about">关于</router-link>
<keep-alive include="Home,About">
<router-view></router-view>
</keep-alive>
</template>

被包裹的组件将会多出两个生命周期钩子, activateddeactivated ,每次被激活或者取消激活状态的时候触发。

组件的 name

组件的 name 属性在 includeexclude 里非常重要。没有 name 就只能用正则匹配了。

include

include 可以是字符串或正则,如果是字符,匹配组件的 name 属性,如果要匹配多个组件,用 , 隔开,并且这里不要随便加空格 ,这样就会不匹配了!,表示被匹配的组件可以有 keep-alive 的效果,也就是多了 activateddeactivated ,并且 created 只会调用一次,destroyed 不会被调用

exclude

exclude 可以是字符串或正则,与 include 作用相反,被匹配的组件没有 keep-alive 的效果,被排除在外,同样不要随便加空格。

同样不能加空格的地方是正则,想匹配 2-9个 数字的时候,我们写成 /\d{2,9}/

但绝对不能写成 /\d{2, 9}/,这是个错误的正则,所以 一般和正则有关的东西,都不要随便加空格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!-- Home 页面 -->
<template>
<div>
Home 页面
</div>
</template>
<script>
export default {
name: 'Home',
created() {
console.log('created,只会在创建的时候被调用,而 keep-alive 使它不会被 destroy,所以只会调用最初的一次之后就不再调用')
},
destroyed() {
console.log('destroyed,不会被调用,因为状态被缓存了,即使离开,组件也不会被 destroy,只会处于不活跃状态')
},
activated() {
console.log('activated,每次被激活的时候都会调用,相当于替代了 created')
},
deactivated() {
console.log('deactivated,每次被取消激活的时候都会调用,相当于取代了 destroyed')
}
}
</script>


<!-- About 页面 -->
<template>
<div>
About 页面
</div>
</template>
<script>
export default {
name: 'About',
created() {
console.log('created,只会在创建的时候被调用,而 keep-alive 使它不会被 destroy,所以只会调用最初的一次之后就不再调用')
},
destroyed() {
console.log('destroyed,不会被调用,因为状态被缓存了,即使离开,组件也不会被 destroy,只会处于不活跃状态')
},
activated() {
console.log('activated,每次被激活的时候都会调用,相当于替代了 created')
},
deactivated() {
console.log('deactivated,每次被取消激活的时候都会调用,相当于取代了 destroyed')
}
}
</script>
分享到:

评论完整模式加载中...如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理