本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!
作为一个从零入门的 Java 小白,虽说没见过什么正规的项目,也没见过大世面,但是对于 Web 开发的发展阶段还是有所体会的(谁还不是从手写 web.xml 开始的呢?!)
后端渲染(SSR、服务端渲染)
我们起初在开发一个 web 应用时,我们都会先写 Servlet 程序,然后在 web.xml 手动配置请求映射。让对应的 Servlet 程序去处理对应页面的请求,从数据库中取出数据然后渲染到 JSP 页面上并返回给用户,而数据渲染到 JSP 的过程,了解的同学应该知道 JSP 最终也会编译为一个 Servlet 程序,然后通过拼接 HTML 字符串完成数据渲染。
这样的做法,导致项目的整体重心都偏向了服务端,所有的工作都得后端一手完成,在那个年代后端工程师的地位不言而喻。
当然也不是贬低 SSR,它也有一些优势:
利于 SEO(搜索优化)
因为服务端渲染后发送到客户端的页面是已经渲染完成的 HTML 代码,所以网络爬虫是可以爬取到完整的页面数据的,更利于搜索引擎收录页面的最大内容,以提升在搜素结果中的权重。
利于首屏渲染
使用后端渲染,所有的渲染工作都在后端完成,也就是说一个请求过去,你就能拿到一个完整的页面数据,客户端直接操作就行。相反当你使用了客户端渲染,当项目规模较大时,大批量的数据加载会导致用户页面长时间白屏,用户体验很不好。
前后端工程师被不公平对待,前端工程师当然不爽,就说:俺们也是人啊,俺们也要吃饭的,加薪!!
老板一想给钱可以,但是不能就干这点活啊,加工作量!
后端也不爽:凭什么他们舒舒服服,俺们就吃苦受累?!不干了。
于是提出了一种折中的办法:后端依旧提供数据并预留好接口,但是不用渲染,前端写好页面,从后端接口取数据进行展示。这就是现在项目开发热词:前后端分离
前后端分离
Ajax 和 JSON 的组合模式在那一年(2005)爆火,就是前后端分离的起点。前端使用异步请求,到后端取出数据,然后用于前端的页面渲染。前端不再依赖于后端的渲染工作,可以独立部署,使用静态资源就能保证项目正常运行,直到现在前后端分离依然是项目开发中火热的词汇。前后端的独立部署所带来的好处就是两者的版本发布不再依赖另一端,保证了开发独立性,做到最终的前后端解耦。
SPA 时代,Single Page web Application 单页面应用
Web 端中开始使用history
、hash
API 完成对 URL 修改。在第一次访问后服务端,应用数据被下载到前端后,此后所有的页面路由都由前端完成。其底层也就是利用前面说到的两个 API。那么现代前端开发,对这两个 API 进行封装,也就有了我们现在所说的前端路由!
好了就先唠这些吧,过于高深的东西还需要我不断的学习,以后再聊~~
既然说了前端路由,那么就不得不提一下这俩。
window.history
location.hash
使用 history 时是需要服务器支持的,但是使用 hash 则不用。
两者对于浏览器页面的控制有点类似:前进/后退 与 锚点。
hash 通过直接在 URL 后拼接锚点,然后跳转到对应的锚点位置。
页面会监听 hash 值,当 hash 变化时会调用hashchange()
,来完成页面的跳转。可以实现局部刷新!
浏览器控制台中使用 location.hash='xxx'
就会将#xxx
拼接到当前 Location(即浏览器地址栏中值)的末尾
history 相当于我们浏览器里面的前进后退按钮
主要使用的方法有back()
、forward()
、go(number:Int)
。
back 就是后退,forward 是前进,go 则取决于你传入的数字,为负数则是后退,为正是前进。具体前进多少步就看你传入值的大小了!
它也有一个方法,可以在不请求后端服务器的情况下,修改 url。pushState()
,三个参数,data、title、url。
使用此方法后,我们的访问记录将会被 push 到一个栈结构中,然后配合使用我们前面讲的三个方法,就可以实现在历史访问记录之间进行跳转。
还有一个replaceState()
效果与之相似(修改 url 而不刷新页面),但是访问记录不会被保存,而是直接替换。
安装 router
如果创建项目的时候,没有选择 router 预设,需要手动下载
npm install router --save
为了方便管理,我们在 src 目录下创建一个 router 文件夹(如果选择了预设,会自动创建),然后创建 index.js
Tips: 我们在使用 import 时,当路径写的是一个文件夹时,会默认导入对应文件夹下的 index.js 中导出的内容。
例如:
import xx from './router'
就会从 router 文件夹下的 index.js 中找导出的 xx。
// 导入VueRouter、Vue进行router启用和配置
import VueRouter from 'vue-router'
import Vue from 'vue'
// Vue.use() 安装插件
Vue.use(VueRouter)
// 定义路径映射关系, 一条映射关系就是一个`Object`
const routers = [{
}]
// 创建VueRouter实例,并传入配置的映射关系
const router = new VueRouter({
routes: routers,
})
// 导出供Vue实例进行使用
export default router
配置完 router,如果我们想让其生效,必须将其配置到 Vue 实例上:
// main.js
import Vue from 'vue'
import App from './App.vue'
// 从对应的index.js中导入router
import router from './router'
Vue.config.productionTip = false
new Vue({
// 在实例中配置router
router: router,
render: (h) => h(App),
}).$mount('#app')
下面就要对我们的路由组件进行注册,首先需要创建我们期望使用路由的组件
当前项目 src 目录下:有一个
views
还有一个components
,两者里面放的均是.vue 文件,这下可把我整懵了,我这到底在哪个文件夹下创建我们的路由视图组件呢?!Tips: 百度查阅后,我们的组件将更具大小分为视图级、容器级(container)、组件级。其中容器级可以根据项目的需要来选择是否启用。这里的视图级和组件级就是我们纠结的问题。我们用于路由的组件通常都划分为视图级组件,放在
views
目录下,而其他页面内部使用的小组件则放在components
中。总结一下,根据组件是否参与 route 来判断其是否放在 view 目录下!(可以看看项目创建初的示例代码,会发现 router index.js 引入的都是 views 目录下的组件,然后 views 下的组件引用 components 下的组件)
Tips:
@
可以代替src
目录,例如/src/views
可以写成@/views
了解完两个目录的差异后,我们在 Views 中创建两个视图组件
// Home.vue
Home
您当前所在的位置:主页
//
==============================================================================
// About.vue
About
您当前所在的位置:关于
然后下一步就是为这两个视图组件配置路径映射了,回到 router/index.js,在 routes 中加上路径映射:
一个路径 path 对应一个组件 component!(首先要导入 views 中的 component!)
// ....
import Home from '@/views/Home'
import About from '@/views/About'
// 定义路径映射关系, 一条映射关系就是一个`Object`
const routers = [
{
path: '/home',
component: Home,
},
{
path: '/about',
component: About,
},
]
// ....
由于我的组件是不会直接渲染到主页的,只有我们 Vue 实例挂载的 template,或者说 Vue 实例中 render 函数渲染的组件才会在主页显示。也就是说默认 App.vue 下的 template 是会在主页进行默认显示的!所以我们需要在 App.vue 的 template 中添加两个按钮或者什么东西保证能修改我们的 url,我们才能看到 router 的效果!
router 提供了一个叫做
的玩意儿,它的to
属性,可以直接写我们在做路径映射时所写的 path!
最终它会被编译成一个 a 标签,点击后就会在 href 后面拼接 path:
<template>
<div id="app">
<div>
<router-link to="/home">Homerouter-link> |
<router-link to="/about">Aboutrouter-link>
div>
<router-view />
div>
template>
代码中的
也就是我们路由的出口,将会替换为 path 匹配的路由组件。
效果展示:
再来看看模板编译后 html 代码:
改进一:路由方式改为 history
默认情况下是使用 hash 方式对 href 进行拼接:
如果我们想要使用 history 模式,需要在创建VueRouter
实例时,加上一个 mode 参数:并设置为'history'
const router = new VueRouter({
routes: routers,
mode: 'history',
})
改进二:设置缺省路径
项目启动后,我们的路径是/
,但是我们的路由映射中并没有设置,我们可以通过配置路由或者使用路由重定向。下面就演示下重定向的一种,后续我们会详细介绍:
const routers = [
{
path: '/',
// 重定向到路径/home
redirect: '/home',
},
{
path: '/home',
component: Home,
},
// ...其他映射
]
改进三:禁用并替换前进、后退
不管使用哪种模式,看似我们只是在两个视图组件之间来回跳转。但是每次跳转都被记录到 history:
并且我们的浏览器的回退和前进按钮是一直可用的,如果你希望强制用户使用你提供的 router-link 进行跳转而不是使用浏览器的回退前进按钮,你可以在
上加上replace
属性。
<router-link to="/home" replace>Homerouter-link> |
<router-link to="/about" replace>Aboutrouter-link>
这样设置以后,无论我们在视图组件中跳转多少次都不会被记录到 history 中。底层是将 push()改为使用 replace()
改进四: 更改 router-link 编译样式
在 router-link 中可以使用tag
属性来设置你希望它被编译为什么元素,默认是a
元素。
例如我们希望渲染成按钮,只需设置tag='button'
<router-link to="/home" tag="button" replace>Homerouter-link> |
<router-link to="/about" tag="button" replace>Aboutrouter-link>
渲染效果:
上面已经了解了 router-link 的to
, tag
, replace
。下面我们针对其他常见的属性进行学习:
active-class
当我们同时有多个 router-link 时,你会发现我们当前所在页面对应的 router-link 被渲染后会加上一串 class:
这就是使用 router-link 的active-class
属性来达到的效果。
我们就可以利用这个特定的 class 做一些样式设计,如果我们需要自定义被激活 router-link 加上的 className 时,可以通过在 router-link 上对 active-class 属性进行设值。(其默认值是"router-link-active")
<router-link to="/home" tag="button" replace active-class="in-home" >Homerouter-link>|<router-link to="/about" tag="button" replace active-class="in-about" >Aboutrouter-link>
然后我们就可以针对这些特殊的 class 来做样式设计了。
如果我们希望 active-class 在所有的 router-link 都一样,没有特殊需求,在 router-link 很多的情况下,一个个手动加上去也太不“洒脱”了。这种情况下,我们在创建 router 实例时,可以用linkActiveClass
进行统一设置!
const router = new VueRouter({
routes: routers,
mode: 'history',
// 统一设置router-link的active-class
linkActiveClass: 'active-link',
})
exact-active-class
当链接精准匹配到 path 时,激活此 class,默认值"router-link-exact-active",一样可以通过 VueRouter 构造参数linkExactActiveClass
进行全局指定。
append
当 route-link 加上此属性后,会自动将 path 拼接到当前路径的后面。
例如当在/a
路径下,跳转到相对路径/b
,如果不加上 append 点击跳转后的路径就是/b
,加上 append 则是/a/b
以上是 router-link 常用常见的属性,更多属性 API 可以查看官方文档:API 参考 | Vue Router (vuejs.org)
我们通常对一些路由路径需要映射到同一个组件,那么就需要使用动态路由匹配。
例如在处理 RESTful 请求时,URL 中是通过数据拼接的,那么我们需要对同一类 URL 进行动态路由匹配:
比如/article/34523
、/article/8782234
都利用同一个路由组件。显然他们有固定的 URL 前缀/article/
,但是后面的数据就可以利用动态匹配,于是我们在写路由的 path 时可以使用path: '/article/:id'
显然我们不能把动态匹配的 id 数据直接丢弃,我们可以在对应的路由组件中通过this.$route.params.id
直接引用到 URL 中的动态数据!
// 路由设置
{
path: '/article/:id/:title',
component: Article
}
Article 组件:
// 路由组件 Article.vue
Article
《{
{ this.$route.params.title }}》
您正在浏览的文章id为 {
{ this.$route.params.id }}
演示效果:
基本的使用应该了解了,我们还可以继续添加动态匹配的参数,来处理更复杂更长的 URL。所有匹配解析的数据都可以通过在该组件模板中通过this.$route.params
取到
你可能会疑问这里的 this.$route 什么?!它从哪来的?!在官方 API 文档中有这样一段话:
也就是这个属性以及后续我会遇到的this.$router
都是在为 Vue 实例配置了 router 属性后,注入到每个组件中的!
为什么这样做呢?
可以避免在所有我们独立封装的路由组件中都导入 router,并且我们在路由组件中对路由信息的依赖性是比较高的,我们的都需要通过它来获取!
再来看看是路由信息对象:这是官方给的解释:
这里我们先暂时记住,一个路由对象代表者当前激活路由(每次重新导航成功后都会生成新的路由对象!)的状态信息,这些信息就包括了从 URL 中解析的数据(我们刚才使用params
就是其中之一),以及 URL 的匹配到的路由记录(有点懵,后面遇到再来细说)。
有需要可以看看路由对象的属性,它们都是不可变的:路由对象属性
知道了这些后,我们大概就知道了我们直接通过this.$route
就可以拿到当前路由的大部分信息。
当我们使用了动态路由匹配,为了提高效率,当仅仅是路由的参数在变化,而使用的是同一个路由组件时,(例如:使用动态路由匹配article/:id
时, 从/article/1234
跳转到/article/5678
)Vue 会选择复用路由组件实例,只是替换数据。(毕竟这比起销毁再创建要更高效!)
但是面临的问题是复用时组件生命周期的钩子将不会再次执行!
如果你想要在参数变化后进行一些响应,你可以选择监控$route
(因为上面我们说过重新导航后都会生成新的路由对象,参数的变化就说明导航成功,那么$route
必定会变化!)
在路由组件中加入以下代码:
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
}
$reoute(to, from)
函数的两个参数 to、from 分别是旧的路由对象和新的路由对象。
你也有第二个选择:导航守卫。我们后面仔细说。
我们先前使用:xxx
对路由进行匹配,但是这种匹配只能对使用/
分割的 URL 参数进行通配。如果你想要对整体路径做通配,你就需要使用通配符*
。例如/article-*
就会处理所有以article-
开头的路由。
{
path: '/article-*',
component: Article
},
{
// 匹配任何路径
path: '*',
component: NotFound
}
匹配的优先级:根据在 routes 中地址映射定义顺序来排,越早定义的优先级越高!!
为了保证尽可能匹配我们已经配置的路由,应该将带通配符的路由放在后面,防止被提前拦截!而使用*
作为路径的路由,我们一般将其放到最后用于应对 404 请求!
我们再回头来看这个通配符路径,被我们匹配到的路径,URL 中被通配符匹配的数据会在路由对象中的params
属性中以pathMatch
为 key 进行保存。
// 路由配置
{
path: '/article-*/:id/:title'
component: Article
}
// 测试的URL: localhost:8080/article-nature/1123/动物与自然
this.$route.params = {
id: 1123,
title: '动物与自然',
pathMatch: 'nature',
}
vue-router 使用 path-to-regexp作为路径匹配引擎,它还支持很多高级匹配方式,包括可选参数、匹配多个甚至使用正则化匹配,如果需要可以通过阅读文档进行使用!
我们现在使用的路由组件都是单个简单的路由组件,可是在实际开发中,我们通常会遇到路由嵌套的问题,即一个路由嵌套中,里面还套着一个路由组件,并且内部的路由组件会根据 URL 的某些参数变化而变化,例如:
你可以看作为是在路由组件下,再进行路由?!!
我们如何来实现这种需求呢?!
外层的 Article 组件渲染,各位应该都应该很熟悉了,关键就是我们如何将内部的路由组件渲染到 Article 组件内?!
标签作为路由的出口,我们目前只在 App.vue 中使用过(用于渲染顶层的 Article 组件),既然是在路由组件内部再进行路由,那么路由组件的 template 中是不是应该也能写
?!
<template>
<div class="article">
<h1>Articleh1>
<router-view>router-view>
div>
template>
这个 route-view 就可以渲染内部的路由组件。下一步就是配置对应的路由路径映射了。
我们需要在路由路径为/article
的路由规则下,进行内层路由配置。当前我们学习的路由配置对象的属性还只有path
和component
以及用于重定向的redirect
,现在我们学习第三个children
这是官方 API 文档中对 Route Config 参数的描述
因为 route 配置中的 children 就是一个 route 配置的数组,所以可以多层嵌套,且配置的属性都是一样的。
route/index.js
{
path: '/article',
component: Article,
children: [
{
// 当匹配到 /article/news Article组件内部会使用此组件作为路由出口
path: 'news/:id',
component: News,
},
{
path: 'blog/:id',
component: Blog,
}
]
},
演示效果:
我们现在有一个想法:将 News 和 Blog 抽离放到 Article 组件下,主页只留Home
、Article
、About
三个导航器:
// App.vue
Home |
Article |
About
然后将 Blog、News 的导航器放到 Article 组件内:
// Article.vue
Article
News
|
Blog
然后就是这个样子:
但是在我们的路由配置中,对于/article
里面没有匹配的子路由,那么也就无法为 Article 组件的 route-view 选择合适的路由组件。你可以在子路由中设置一个缺省路由:
children: [
// ...其他路由
{
// 当url为 /article 会匹配到此路由
path: '',
component: ArticleHome,
},
]
效果演示:
关于使用代码进行页面的跳转
目前我们都是使用 router-link,来自动帮我们完成页面路径的修改从而完成页面跳转,如果我们希望通过事件并结合方法用代码完成页面跳转要怎么实现呢?!
我们之前学习 history 模式时,讲到过使用history.pushState()
可以控制 href 变化,但是在有 Vue-Router 的情况下,我们这样绕过 vue router 的做法并不推荐!我们应该考虑使用路由实例即 router 来完成:
也就是我们马上要学习的编程式导航
官方文档教程中,告诉我们可以使用router.push(location, onComplete?, onAbort?)
方法来实现与history.pushState()
一样的效果。我们要如何拿到这个 router 对象呢?router 又代表什么?
翻翻前面我们说过的$route
,里面说了组件注入,为 Vue 实例设置 router 时,会为每个组件都注入两个属性,一个是$route
,另一个就是$router
。
前者是保存了当前激活路由信息的一个对象,而后者则是表示 router 实例!(即我们在 router/index.js 中创建的 VueRouter 对象!)
为什么注入?因为频繁使用,并且是一个全局的变量,每次都手动 import 很麻烦!干脆给每个组件都注入一个属性让他们都通过这个属性去访问这个全局的变量!
也就是说我们在任何地方使用this.$router
和导入再使用 router 实例效果是一样的!!
编程式导航涉及的 router 的实例方法:
是不是感觉和 HTML 中 history 的 API 很像!?!(但是**使用 route 导航方法,对路由模式不做任何要求。**即不管模式为 history、hash、abstract 哪一种,使用导航方法的效果都一样!!)
通过这些方法,我们就可以选择用具体的事件来控制导航,而不是使用route-link
。
声明式 | 编程式 |
---|---|
|
router.push(...) |
我们现在将我们前面我们使用的 route-link 都换做使用按钮点击事件中的导航方法实现:
|
|
使用router.push()
会有 history 存留的问题,如果希望达到 router-link replace 的效果(history 不存留记录),可以使用router.replace()
!
虽然使用这种方式可以定制事件触发导航,但是很多东西也就用不了了,例如active-class
。
router.push(location, onComplete?, onAbort?)
第一个参数不仅可以是一个字符串路径也可以是一个描述地址的对象:
例如:原先的
可以替换为
// 匹配的路由路径: /article/:id
this.$router.replace('/article/1234')
this.$router.replace({
// 命名的路由 (需要对应的路由已经命名了!!)
name: 'Article',
params: {
id: 1234,
},
})
同样可以写在 route-link 的 to 属性值内:
<route-link to="{ name: 'Article', params: { id:1234 } }">route-link>
注意让我们写了path 属性时,完整 URL 路径,那么params 参数就会失效!!
this.$router.replace({
// 会认为此path就是完整的URL,params失效!
path: '/article',
params: {
id: 1234,
},
})
如果要使用对象来描述,那么我们的描述地址对象中使用的 name,指向一个已经命名的路由的 name 属性!!
例如:
路由配置:
// route/index.js
const routers = [
// ... 其他路由映射
{
path: '/article',
component: Article,
children: [
{
// 命名的路由,name属性
name: 'News',
path: 'news/:id',
component: News,
},
{
// 命名的路由
name: 'Blog',
path: 'blog/:id',
component: Blog,
},
{
path: '', component: ArticleHome },
],
},
]
路由组件:
// Article.vue
//当我们使用命名路由时,我们只需要指定对应的参数就行了!!也就是使用params属性:
Article
|
总结:参数中 name 属性值中不能出现与路径相关的东西,直接使用对应命名路由的 path,只需要设定对应的 URL 参数就行了。如果使用的 path 属性,那么默认就使用 path 属性值作为 URL,params 就失效!
第二、三个参数onComplete
,onAbort
表示两个回调函数,当导航成功、失败后会触发对应的钩子然后执行这两个回调函数。
在配置路由的时候,加上name
属性,此属性值与route-link
中to
属性的属性值对象的 name 属性对应时,会直接使用对应的路由,参数可以通过 params 携带。或者使用this.$router.push()/replace()
时,与路径描述对象中 name 属性相同,效果一样。
const routers = [
// ... 其他路由配置
{
path: '/article',
component: Article,
children: [
{
name: 'News', path: 'news/:id', component: News },
{
name: 'Blog', path: 'blog/:id', component: Blog },
{
path: '', component: ArticleHome },
],
},
]
编程式导航使用命名的路由:
this.$router.replace({
name: 'News',
params: {
id: 34234,
},
})
// 先取到对应name的路由完整URL: /article/news/:id
// 然后将params中参数名一致的参数值替换到URL中: /article/news/34234
声明式:(只是将方法参数写到 to 的属性值中)
<route-link to="{ name: 'News', params: { id: 34234 } }">route-link>
前面我们在使用路由时,template 中最多也就一个route-view
,但是通常情况下,我们的一个页面中都是由多个路由组件组合而成的,区别于前面的嵌套路由,这些组件应该是在同一级:
那么也就是说我们需要在一个 template 中写多个route-view
,可面临的问题是,被渲染的组件,哪里知道它应该被放在那个视图,于是我们需要为这些路由视图取名,在渲染时通过视图名指定即可!
首先我们要增加 App.vue 中的 template 里面 route-view,然后为他们设置name
属性:
<template>
<div id="app">
<router-view name="nav">router-view>
<router-view name="hot">router-view>
<router-view>router-view>
div>
template>
不设置 name 属性时,默认视图名为default
然后我们需要修改我们的路由,因为最开始设置时,每条路由路径我们只指定了一个 componet:现在我们要同时为每条路由路径设置三个 component,属性名从component
变为components
,且在指定组件时,视图名作为键,使用的组件作为值:
{
path: '/home',
// 同时指定三个component,分别对应App.vue中的三个视图
components: {
nav: Nav,
hot: Hot,
default: Content
},
children: [
{
name: 'home',
path: '',
components: {
articleContent: Home},
}
]
},
演示效果:
一个关于嵌套路由和多视图的问题:
例如你现在的三个视图中,你渲染的组件内部可能还存在视图,就像这样:
那么他们就存在一个层级问题,你要如何将具体的组件渲染到一个组件中的组件呢?!
首先我们在顶层写 components 时,必须对应顶层的 route-view,即只能为Nav
, Content
,Article-List
三个视图设置对应的组件!就算你多写了,在顶层找不到对应 name 的视图是渲染不上去的!!(根本不关心视图组件嵌套的问题!)
如果我们需要将组件渲染到组件内部的组件,这就是嵌套路由的问题了,我们需要在 children 中配置下一层级中的视图即Info
, Main
,在同一层级下,可以对**当前层级下所有的视图(包括 Nav 等同层视图中的嵌套视图)**进行配置!
{
path: '/home',
components: {
// 这里写的组件数 应该与顶级视图的数量相同
Nav: xxx,
Article-List: xxx,
Content: xxxx,
}
children: {
name: 'home',
path: '',
components: {
// 在这里配置下一层级的视图,数量也应该与当层视图数量相同
Main: xxx,
Info: xxxx
}
}
}
如果嵌套中还有嵌套,那就要在 children 中继续写 children。
反正从你的顶层路由入口开始,到你的路由出口,你设置的视图数量,应该与你页面的上设置的视图数量相同!!这个路由配置就像一颗树一样,一个路由入口,最终可能分出 n 个路由出口!
路由重定向,前面在对入门案例就像改进时,我们已经使用过了。
重定向到指定路径
{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home
}
重定向到对应的路由
重定向的路由,必须有 name 属性(即命名路由)
{
path: '/',
redirect: {
name: 'home' }
},
{
name: 'home'
path: '/home',
component: Home
}
通过函数获取重定向路径
函数的参数是被重定向的路由信息(即一个路由信息对象)
返回一个重定向的路径或者一个路径描述对象
{
path: '/',
redirect: to => {
return xxxxx
}
},
与重定向的区别是,重定向会修改地址框中的地址,例如你访问/a 重定向到/b,那么地址框也会随之变化。
但是你使用别名,将/b 的别名设置为/a, 你访问/a 一样是匹配/b 的路由,但是路径以及保持/a 不会修改为/b
为路由设置路径别名
{
path: '/b',
component: xxx,
alias: '/a'
}
这样的设置意味着访问路径/a,/b 都是一样的效果,但是用户并察觉不到!
当我们在路由组件内部使用this.$route.params
进行取值时,组件将会与路由的URL产生强烈的耦合关系!从而使得组件只能在特定的URL下使用,大大限制了组件的使用场景。
于是我们想到了使用props
对其进行解耦。进行路由的时候,通过props进行传参,将数据设置为组件的属性,在组件模板中可以像使用data一样使用。(这里的思想可以参考 父组件通过props向子组件传参的思想。)
下面我们借助一个官方所给的示例来看看,怎么用props
路由配置:
import Hello from '@/views/Hello'
function dynamicPropsFn (route) {
const now = new Date()
return {
name: (now.getFullYear() + parseInt(route.params.years)) + '!'
}
}
// 定义路径映射关系, 一条映射关系就是一个`Object`
const routers = [
{
path: '/',
component: Hello
}, // No props, no nothing
{
path: '/hello/:name',
component: Hello,
props: true
}, // Pass route.params to props
{
path: '/static',
component: Hello,
props: {
name: 'world' }
}, // static values
{
path: '/dynamic/:years',
component: Hello,
props: dynamicPropsFn
}, // custom logic for mapping between route and props
{
path: '/attrs',
component: Hello,
props: {
name: 'attrs' }
}
]
主页组件App.vue
Route props
/
/hello/you
/static
/dynamic/1
/attrs
通用的路由组件:Hello.vue
Hello Component
{
{ name }} + {
{ $attrs }}
使用效果:
建议好好理解一下代码后,再来进行解析:
你会发现在路由设置中,我们对Hello
这个Component进行复用。也就是我们将组件和路由进行了基本的解耦,他们不再相互依赖!
先看看Hello组件
我们在里面并没有使用this.$route.params
这种方式来取route对象中的数据!这是我们解耦的第一步!
组件模板中我们使用{ { name }}
获取外部传入的数据,并在组件内部在props
对象中的定义了name
属性来接收外部给我们传入的具体数据!
而$attrs
,始终都是{ "foo": "123" }
这是属于非props的attribute(同时包括class = "view"
),会直接加到组件模板的根元素上!(忘记了的,建议回去复习一下Vue.js中的父子组件通信!!)
所以这个name是我们研究的重点。
再来看看路由设置
第一个:
{
path: '/',
component: Hello
},
<li><router-link to="/">/router-link>li>
组件渲染出来的name是Vue
,此时使用的name属性的默认值。
第二个:
{
path: '/hello/:name',
component: Hello,
props: true
},
<li><router-link to="/hello/you">/hello/yourouter-link>li>
在路径中,使用了:name
来动态匹配路由,显然匹配的内容是you
,但是过去我们在组件使用这个匹配值的时候,应该是this.$route.params.name
,(可是这样就限制了,对应的组件的路由URL必须对name做了匹配,如果没有就无法取到值!)
目前这样的路径显然是对name做了匹配的,所以不受影响,原来的方式一样能取到值。可是现在我们在组件中通过props取值,我们如何将匹配值传给组件的props?!这里我们第一次遇见组件传值:)
我们在路由设置中将props
设置为true
时(布尔模式),this.route.params中的属性值都会被按照属性名传给组件内部的props,然后组件模板内部中在渲染时,会将对应的值替换上去!(模板只能使用props中已经定义的!即使外部多传了,内部也是拿不到的!!)会被视为非props属性,直接加在模板根元素上!!
例如:
// 路由配置
{ path: '/hello/:name/:id', component: Hello, props: true },
// 测试URL
<li><router-link to="/hello/you/2233">/hello/yourouter-link>li>
这里的id也被加入了组件属性,但是因为组件props中间并没有定义id,所以被视为是非prop的属性
第三个:
{
path: '/static',
component: Hello,
props: { name: 'world' }
},
// 测试URL
<li><router-link to="/static">/staticrouter-link>li>
通过上面的演示动图,可以看出name渲染值是world
。上面我们学习了布尔模式,即将props设置为bool值。这里我们学习的就是对象模式!
路由直接为props传入一个对象,一样通过属性名对应设置值。(同样组件内部没有定义的,会视为非props属性,会加到…)。这种传值模式,必须保证你的props的值是静态值,例如字符串、数字
第四个:
{
path: '/dynamic/:years',
component: Hello,
props: dynamicPropsFn
},
function dynamicPropsFn (route) {
const now = new Date()
return {
name: (now.getFullYear() + parseInt(route.params.years)) + '!'
}
}
测试URL:
<li><router-link to="/dynamic/1">/dynamic/1router-link>li>
这就是我们最后一种方式了:函数模式!props属性值设置为一个函数,函数的参数是route对象(路由信息对象),返回值是一个对象,这个对象最终会传入给组件进行props设值!
这样做的话,我们在函数内部可以通过route.params
取出URL匹配的值,然后进行一些处理后再传给组件!
譬如上述的例子,URL匹配的属性名明明是year,此时和组件内部定义的propsname
不对应,你用布尔模式,对象模式都不好使!(对象模式中,你无法取到匹配值)。但是通过函数模式,做一个中间转换就so easy!!
导航?!
在学习导航守卫前,我们很有必要学习一下什么是导航?!
官方首页上译者的注解是:**导航就是路由正在发生变化!**我就粗略理解为我在从一个路由到达目标路由的过程。(就像飞机从一个机场起飞到另一个机场落地的过程)
导航守卫是什么?有什么用?
见名知意,导航守卫就是我们导航的控制者,它们被提前布置在我们的航线上,导航守卫可以通过跳转、取消操作来控制导航。
导航守卫分为:全局的、单个组件独享的、组件级的
既然是控制导航的,那么有些情况下我们的URL参数变化但是不会产生导航,例如路由参数值变化,查询值变化都是不会出现导航的!(因为路由没有变化!!)所以也就不会触发进入、离开导航守卫!
如果想要监视这个过程,需要使用watch监视route对象的变化!或者使用组件内部的守卫 beforeRouteUpdate
学习此导航守卫前,可以先看看官网的文档说明。通过学习和猜测,我暂时将这个前置守卫看做是一个过滤器 + 分流器
。
全局的前置导航守卫会按照创建的顺序在触发导航时顺次调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。(也即是说我们可以同时存在多个前置导航守卫!)
创建全局前置导航守卫
const router = new VueRouter({
routes: routers,
mode: 'history',
linkActiveClass: 'active-link'
})
// 创建全局前置守卫
router.beforeEach(
// 守卫
)
重点是我们的守卫如何创建?!
守卫其实就是一个函数,在触发时会异步执行。守卫函数需要三个参数:
to
: 目标路由的信息对象(Route) 忘记了什么是路由信息对象的,去翻10.6.1from
:也是当前的路由信息对象,即导航的起点。next
:一个function,当守卫被触发进行resolve时,一定要调用这个函数,这个函数的执行效果取决于调用时传入的参数。大概分为以下几种:
next()
,进入下一个守卫进行resolve。如果所有守卫都处理完了,导航状态变为confirmed!next(false)
,中断当前导航。并且将URL重置为from路由中的地址!next('/path')
或next({ name: 'xxx', params: {xx: 'xxx'}})
:传入一个字符串路径或者一个路径描述对象(参考复习编程式导航router.push的参数)。中断当前导航,开始一个新的导航,跳转到指定位置。next(error)
,error
是一个Error实例,此时导航被中断,调用在router.onError()
中注册过的回调函数并传入Error实例函数为参数。特别说明,在所有注册的守卫中,一定要保证next函数被执行一次!可以出现多次,但是终止执行的只能有一次,否则这个钩子永远不会被resolve
官方给了一个未登录状态下请求重定向的案例:
这里就不额外使用例子了,理解好官方给的例子就足够了。
文档中没有具体说明,我是个菜鸡,也不太懂,以后弄懂了再来补上~~
你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数
router.afterEach((to, from) => {
// ...
})
**注意:因为不接受next函数,所以它并不能像守卫那样控制导航。**不过通过目标路由信息和起始路由信息就能做很多事情了!
前面的守卫创建都是全局的,也就是全局的导航都会触发,但是对于特定的路由我们需要设置一些特殊的守卫,并且只能用于此路由。这就属于路由独享守卫!那么我们就不能使用路由配置实例router
进行注册了,而是在路由配置项中进行注册:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
// 此路由独享的守卫
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
通过名字也能看出来,当导航在进入此路由之前需要触发这个守卫进行resolve
很多情况下,我们会对多个路由使用同一个组件。可能我们希望针对这个组件注册一些守卫,那此时我们注册的位置就变成了组件的代码中:一下是官方文档中的例子。
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`
}
}
还记不记得我们在10.6.2响应参数变化那节说过,当URL中只有路由的参数发生变化,并不会进行导航且组件会被复用,如果需要处理这个变化,我们可以使用监听route对象,还有一个选择就是使用导航守卫beforeRouteUpdate
,这里就提到了!当组件被复用时,这些导航守卫仍然生效!
注意:
beforeRouteEnter
中是不能使用this来访问组件实例的!!因为路由还是待确认状态,我们的组件还没有进行实例创建!!!
其他具体的使用细节,参考官网文档的说法,我就不做搬运了~~
是,这些钩子不会接受 next
函数
router.afterEach((to, from) => {
// ...
})
**注意:因为不接受next函数,所以它并不能像守卫那样控制导航。**不过通过目标路由信息和起始路由信息就能做很多事情了!
前面的守卫创建都是全局的,也就是全局的导航都会触发,但是对于特定的路由我们需要设置一些特殊的守卫,并且只能用于此路由。这就属于路由独享守卫!那么我们就不能使用路由配置实例router
进行注册了,而是在路由配置项中进行注册:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
// 此路由独享的守卫
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
通过名字也能看出来,当导航在进入此路由之前需要触发这个守卫进行resolve
很多情况下,我们会对多个路由使用同一个组件。可能我们希望针对这个组件注册一些守卫,那此时我们注册的位置就变成了组件的代码中:一下是官方文档中的例子。
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`
}
}
还记不记得我们在10.6.2响应参数变化那节说过,当URL中只有路由的参数发生变化,并不会进行导航且组件会被复用,如果需要处理这个变化,我们可以使用监听route对象,还有一个选择就是使用导航守卫beforeRouteUpdate
,这里就提到了!当组件被复用时,这些导航守卫仍然生效!
注意:
beforeRouteEnter
中是不能使用this来访问组件实例的!!因为路由还是待确认状态,我们的组件还没有进行实例创建!!!
其他具体的使用细节,参考官网文档的说法,我就不做搬运了~~