一、URL中的#号
1、简介
在单页面应用程序 (SPA - single page application) 中,URL中一般都会包含#。
井号#(Hash): 位置标识符, 代表网页中的一个位置,用于前端的URL中(注意:井号#只用于前端URL中不能在HTTP请求中使用,#是用来让浏览器滚动的,对服务器端完全无用),其右面的字符就是该位置的标识符。比如http://www.example.com/index.html#location1
就代表index.html中的location1位置。浏览器读取这个URL后,会自动将location1位置滚动至可视区域。在第一个#后面出现的任何字符,都会被浏览器解读为位置标识符。
为网页位置指定标识符,有两个方法:
- 一是使用锚点,比如
- 二是使用id属性,比如
2、注意事项
- 大部分使用的是#号,也有使用”#!"的。
- 单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
- 每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。这对于ajax应用程序特别有用,可以用不同的#值,表示不同的访问状态,然后向用户给出可以访问某个状态的链接。
- window.location.hash这个属性可读可写。读取时,可以用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。
- 当#值发生变化时,就会触发onhashchange事件, 可以通过如下三种方式来绑定事件,对于不支持onhashchange的浏览器,可以用setInterval监控location.hash的变化。
window.onhashchange = func;
<body onhashchange="func();">
window.addEventListener("hashchange", func, false);
3、示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>浏览器中的#号作用</title>
</head>
<body onhashchange="hashChange()">
<div>
<div style="position:fixed; right: 0px; bottom: 0px; width:100px;">
<a href="#location1">滚动到位置1</a>
<button onclick="scrollToLocation('/location2')">滚动到位置2</button>
<button onclick="scrollToLocation('location3')">滚动到位置3</button>
<a href="#topLocation">回到顶部</a>
</div>
</div>
<a name="topLocation"></a>
<div id="location1" style="height: 700px; background-color: beige">
location1
</div>
<div id="/location2" style="height: 700px; background-color: blueviolet">
location2
</div>
<div id="location3" style="height: 700px; background-color: brown">
location3
</div>
<script>
function hashChange() {
console.log('位置标识符改变:' + window.location.hash)
}
function scrollToLocation(location) {
window.location.hash = location
}
</script>
</body>
</html>
二、vue-router简介
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的、基于组件的路由配置
- 路由参数、查询、通配符
- 基于 Vue.js 过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的 CSS class 的链接
- HTML5 历史模式或 hash 模式,在 IE9 中自动降级
- 自定义的滚动条行为
路由:就是“页面”之间跳转,因Vue.js是单页应用(single page application,SPA),即组件之间的切换。
三、 声明式路由< router-link to="/path" >
路由开发步骤:
- 开发组件(component)
- 配置路由(routes)
- 渲染:路由链接(< router-link to="/path">)和路由视图(< router-view>< /router-view>)
- 将组件 (component) 映射到路由 (routes),然后告诉 Vue Router哪个链接(< router-link to="/path">) 要在哪里(< router-view>)来渲染它们。
1、开发组件
Foo.vue
<template>
<div>
Foo
</div>
</template>
<script>
export default {
name: 'Foo'
}
</script>
<style scoped>
</style>
Bar.vue
$route.params.路径变量: 用于获取路由的路径变量, 就像Java中的@PathVariable一样。监听动态路径参数的变化可以通过watch: { ‘$route’ (to, from) { } }或者beforeRouteUpdate (to, from, next) { }, 两者的区别是watch没有next 参数。
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
},
beforeRouteUpdate (to, from, next) {
next()
}
<template>
<div>
Bar {{ this.$route.params.id }}
</div>
</template>
<script>
export default {
name: 'Bar',
created () {
this.init()
},
beforeRouteUpdate (to, from, next) {
console.log(to)
console.log(from)
console.log(next)
// 需要调用next()才会继续路由,就像拦截器一样放开拦截
next()
this.init()
},
methods: {
init () {
console.log('Bar created ' + this.$route.params.id)
}
}
}
</script>
<style scoped>
</style>
2、配置路由(src/router/index.js)
关于path,可以使用路径参数(:参数名)的形式,也可以使用更加复杂的正则表达式。这种称之为“动态路由”
使用表达式当匹配到多个路径时优先匹配第一个满足条件的路径。
注意:当多个路径路由到同一个组件时,因为组件会被复用,所以此时对于生命周期钩子只会调用一次。可以通过beforeRouteUpdate函数来监听路由的变化,在函数体内处理一些逻辑,比如将created的逻辑提取到某个方法中,然后在created和beforeRouteUpdate分别调用该方法。
// 导入vue-router,vue-router需要依赖vue
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Bar from '../components/Bar'
// 使用vue-router插件
Vue.use(Router)
// 导出Router对象,以便被其它文件(main.js)导入
export default new Router({
routes: [
{
path: '/foo',
component: Foo
},
{
path: '/bar/:id',
name: 'bar',
component: Bar
}
]
})
3、src/main.js
import Vue from 'vue'
import App from './App'
// 导入Router
import router from './router'
/* eslint-disable no-new */
// 引用router
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
4、App.vue
使用<router-link to="/path">
配置页面跳转链接,<router-link to="/path">
将被解析成<a href="#/path" class="router-link-active">
如果路径匹配成功后还会自动设置class=“router-link-active”
。
<router-view>
用于定义跳转页面的模板内容将在哪个地方展示,即跳转页面的内容将会把<router-view>
标签替换成具体的模板内容
<template>
<div id="app">
Vue Router
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar/1">Go to Bar 1</router-link>
<router-link :to="{name: 'bar', params: {id: 2}}">Go to Bar 2</router-link>
</p>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
四、编程式路由this.$router.push(…)
1、this.$router.push(…)
使用<router-link>
标签来切换组件称之为声明式路由。也可以通过编程的方式来调用路由器router的push方法来切换组件, 调用router.push(…)方法会向 history栈添加一个新的记录,所以当用户点击浏览器后退按钮时,则回到之前的URL。实际上声明式路由内部也会调用router.push(…)方法。
// 添加组件(会将路径添加到history栈中)
router.push(location, onComplete?, onAbort?)
// 替换组件(不会将路径添加到history栈中)
router.replace(location, onComplete?, onAbort?)
// 前进或者后退
router.go(n)
-
location : 切换组件的路径,可以是一个纯字符串的路径,或者是一个路径对象(可以包含组件名称name、路径path、参数params、查询参数query)。
-
onComplete:可选回调函数,将会在导航成功完成 (在所有的异步钩子被解析之后)后回调。
-
onAbort:可选回调函数,终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由)时调用。
// 字符串
router.push('foo')
// 对象
router.push({ path: 'foo' })
// 命名的路由(注意:params不能与path结合使用)
// name:是指都会配置路由时的name值
// 这种方式参数不会追加在路径后面
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /register?plan=private
// 这种方式会将参数追加到路径后面
router.push({ path: 'register', query: { plan: 'private' }})
- router.replace跟router.push很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
2、router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)
// 后退一步记录,等同于 history.back()
router.go(-1)
// 前进 3 步记录
router.go(3)
// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
3、使用示例
Foo.vue
<template>
<div>
Foo
</div>
</template>
<script>
export default {
name: 'Foo'
}
</script>
<style scoped>
</style>
Bar.vue
<template>
<div>
Bar query: {{ this.$route.query }}
</div>
</template>
<script>
export default {
name: 'Bar',
created () {
this.init()
},
beforeRouteUpdate (to, from, next) {
console.log(to)
console.log(from)
console.log(next)
// 需要调用next()
next()
this.init()
},
methods: {
init () {
console.log('Bar created ' + this.$route.params.id)
}
}
}
</script>
<style scoped>
</style>
Foobar.vue
<template>
<div>
Foobar {{ this.$route.params.id }}
</div>
</template>
<script>
export default {
name: 'Foobar'
}
</script>
<style scoped>
</style>
src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foobar from '../components/Foobar'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/foo',
component: Foo
},
{
path: '/bar/:id',
component: Bar
},
{
path: '/foobar',
name: 'Foobar',
component: Foobar
}
]
})
App.vue
<template>
<div id="app">
Vue Router
<p>
<button @click="jumpFoo">字符串foo</button> <br>
<button @click="$router.push({path: 'foo'})">对象{path: 'foo'}</button> <br>
<button @click="$router.push({name: 'Foobar', params:{id: 1}})">命名的路由 {name: '', params: {id: 1}}"</button> <br>
<button @click="$router.push({path: '/bar/666', query:{age: 2}})">带查询参数<{path: '', query: {}}</button> <br>
<button @click="$router.go(-1)">后退一步</button> <br>
</p>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
jumpFoo() {
this.$router.push('foo', function () {
console.log('onComplete: 跳转完成')
}, function (err) {
console.log('onAbort')
})
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
五、导航守卫
导航守卫(导航的生命周期):用于监听路由发生变化的各个生命周期。可以在多个地方来监听路由的变化,如全局的, 单个路由独享的, 或者组件级的。
- router.beforeEach(to, from, next) => { }) 全局前置守卫
- to : Route类型,即将要进入的目标路由对象。
- from: Route类型,当前导航正要离开的路由对象。
- next:Function类型,用于决定接下来如何导航,如进行下一个钩子、 中断当前导航、跳转到其它地址等。确保要调用 next 方法,否则钩子就不会被 resolved。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。- next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
- next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’、redirect 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
- next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
- beforeEnter: (to, from, next) => { } 路由独享的守卫, 配置在路由中
- beforeRouteEnter(to, from, next) { } 组件内的守卫,在组件内部使用,在渲染该组件的对应路由被 confirm 前调用
- beforeRouteUpdate (to, from, next) { } 组件内的守卫,在组件内部使用,在当前路由改变,但是该组件被复用时调用,举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
- beforeRouteLeave (to, from, next) { } 组件内的守卫,在组件内部使用,导航离开该组件的对应路由时调用。
完整的导航解析流程:
-
导航被触发。
-
在失活的组件里调用离开守卫。
-
调用全局的 beforeEach 守卫。
-
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
-
在路由配置里调用 beforeEnter。
-
解析异步路由组件。
-
在被激活的组件里调用 beforeRouteEnter。
-
调用全局的 beforeResolve 守卫 (2.5+)。
-
导航被确认。
-
调用全局的 afterEach 钩子。
-
触发 DOM 更新。
-
用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
使用示例
App.vue
<template>
<div id="app">
<button @click="$router.push('/foo')">foo</button> <br>
<button @click="$router.push('/bar')">bar</button> <br>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
src/main.js 全局监听路由变化
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
Vue.config.productionTip = false
Vue.use(ElementUI)
router.beforeEach((to, from, next) => {
console.log('全局前置守卫beforeEach')
console.log(to)
console.log(from)
console.log('------------------')
next()
})
router.afterEach((to, from) => {
console.log('全局后置钩子afterEach')
console.log(to)
console.log(from)
console.log('------------------')
})
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
src/router/index.js 单个路由的导航监听
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Bar from '../components/Bar'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
console.log('路由独享的守卫beforeEnter')
console.log(to)
console.log(from)
console.log('------------------')
next()
}
},
{
path: '/bar',
component: Bar
}
]
})
Foo.vue 组件内部导航路由的监听
<template>
<div>
Foo 路由守卫
</div>
</template>
<script>
export default {
name: 'Foo',
beforeRouteEnter (to, from, next) {
console.log('beforeRouteEnter')
console.log(to)
console.log(from)
console.log('------------------')
next()
},
beforeRouteUpdate (to, from, next) {
console.log('beforeRouteUpdate')
console.log(to)
console.log(from)
console.log('------------------')
next()
},
beforeRouteLeave (to, from, next) {
const answer = window.confirm('你确定要离开吗?')
if (answer) {
console.log('beforeRouteLeave')
console.log(to)
console.log(from)
console.log('------------------')
next()
} else {
next(false)
}
}
}
</script>
meta(路由元信息)
meta用于配置路由的元数据,可以理解为为该路由配置一些静态参数,然后在获取到目标路由时获取这些参数。
比如:为每个路由配置是否需要登录才能访问,有的路由需要登录,有的路由不需要登录,可以配置一个全局导航,判断一下目标路由是否需要登录,如果需要登录再检查是否已经登录,如果没有登录就跳转到登录界面,否则跳转到目标组件。
App.vue
<template>
<div id="app">
<button @click="$router.push('/foo')">foo</button> <br>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Login from '../components/Login'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/foo',
component: Foo,
meta: { requiresAuth: true }
},
{
path: '/login',
component: Login
}
]
})
src/main.js
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
let isNotLogin = true;
if (isNotLogin) {
next({
path: '/login',
query: { redirect: to.fullPath}
})
} else {
next()
}
} else {
next()
}
})
六、参数转属性
组件在this.$router.push(...)
时可以携带参数(动态路径、params、query),我们在目标组件中可以通过{{ this.$route.params.参数名 }}
或者{{ this.$route.query }}
来获取参数, 我们也可以将参数转换成属性props, 然后直接使用 {{ 参数名 }}来获取。
props可以为布尔类型或者函数类型,布尔类型一般用于动态路径,函数类型更加灵活强大。在配置路由时需要配置props, 在目标组件也要定义出所有可用的props。
App.vue
<template>
<div id="app">
<button @click="$router.push('/foo/2')">动态路径转属性</button> <br>
<button @click="$router.push({name: 'Foobar', params:{nickname: 'mengday', status: 1}})">params转属性</button> <br>
<button @click="$router.push({path: '/bar/666', query:{username: 'mengday', age: 2}})">query转属性</button> <br>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
Foo.vue
<template>
<div>
Foo <br>
this.$route.params.id={{ this.$route.params.id }}<br>
{id} = {{id}}
</div>
</template>
<script>
export default {
name: 'Foo',
props: ['id']
}
</script>
Bar.vue
<template>
<div>
Bar {{ this.$route.params.id }} <br>
{id} = {{id}} <br>
{username} = {{username}} <br>
{age} = {{age}} <br>
{baz} = {{baz}} <br>
</div>
</template>
<script>
export default {
name: 'Bar',
props: ['id', 'username', 'age', 'baz']
}
</script>
Foobar.vue
<template>
<div>
Foobar <br>
{nickname} = {{ nickname }} <br>
{status} = {{ status }} <br>
</div>
</template>
<script>
export default {
name: 'Foobar',
props: ['nickname', 'status']
}
</script>
src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foobar from '../components/Foobar'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/foo/:id',
component: Foo,
props: true
},
{
path: '/bar/:id',
component: Bar,
props: (route) => ({
id: route.params.id,
username: route.query.username,
age: route.query.age,
baz: 'baz'
})
},
{
path: '/foobar',
name: 'Foobar',
component: Foobar,
props: (route) => ({
nickname: route.params.nickname,
status: route.params.status
})
}
]
})
七、重定向与别名
“重定向”的意思是,当用户访问 /a时,URL 将会被替换成 /b,然后匹配路由为 /b。
别名:就是为一个组件的path另起一个别名,path和alias访问任意一个路径都能路由到对应的组件。
src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foo2 from '../components/Foo2'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/index',
redirect: '/foo'
},
{
path: '/index2',
redirect: {name: 'Bar'}
},
{
path: '/foo',
component: Foo
},
{
path: '/bar',
name: 'Bar',
component: Bar
},
{
path: '/foo2',
alias: '/foo22',
component: Foo2
}
]
})
App.vue
<template>
<div id="app">
<button @click="$router.push('/index')">redirect: '/foo'</button> <br>
<button @click="$router.push('/index2')">redirect: {name: Bar}</button> <br>
<button @click="$router.push('/foo2')"> foo2 </button> <br>
<button @click="$router.push('/foo22')"> alias </button> <br>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
八、组件嵌套
一个单页面应用是一个根组件(App.vue),根组件中会嵌套多个子组件,子组件中又会嵌套多个孙子组件。总之一个单页面应用是由无数个子组件嵌套而成,就像一棵倒立的树结构一样。
在上一个示例的基础上,写一个嵌套结构的组件。
Foo1.vue
<template>
<div>
Foo1
</div>
</template>
<script>
export default {
name: 'Foo1'
}
</script>
<style scoped>
</style>
Foo2.vue
<template>
<div>
Foo2
</div>
</template>
<script>
export default {
name: 'Foo2'
}
</script>
<style scoped>
</style>
src/router/index.js
组件可以通过children属性来配置子组件。
import Vue from 'vue'
import Router from 'vue-router'
import Foo from '../components/Foo'
import Bar from '../components/Bar'
import Foo1 from '../components/Foo1'
import Foo2 from '../components/Foo2'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'foo1',
component: Foo1
},
{
path: 'foo2',
component: Foo2
}
]
},
{
path: '/bar/:id',
component: Bar
}
]
})
Foo.vue
<template>
<div>
Foo
<p>
<router-link to="/foo/foo1">Go to Foo1</router-link>
<router-link to="/foo/foo2">Go to Foo2</router-link>
</p>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Foo'
}
</script>
<style scoped>
</style>
九、命名视图
命名视图就是给<router-link>
设置一个name属性,name属性的值就是在配置路由时指定的name值,设置了name意思就是该视图只用于展示name对应的组件内容,而不会展示别的组件的内容。
当在同级展示多个视图(< router-view >
),而不是嵌套里面。一个视图(< router-view >
)对应一个组件(component), 多个视图就对应多个组件。在配置路由时使用components来配置多个组件,同时给每个组件起一个名称。在使用视图时通过名称来指定该视图对应的组件。
Top.vue
<template>
<div>
Top
</div>
</template>
<script>
export default {
name: 'Top'
}
</script>
<style scoped>
</style>
Left.vue
<template>
<div>
Left
</div>
</template>
<script>
export default {
name: 'Left'
}
</script>
<style scoped>
</style>
Main.vue
<template>
<div>
Main
</div>
</template>
<script>
export default {
name: 'Main'
}
</script>
<style scoped>
</style>
src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Top from '../components/Top'
import Left from '../components/Left'
import Main from '../components/Main'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
components: {
top: Top,
left: Left,
main: Main
}
}
]
})
App.vue
<template>
<div id="app">
<router-view class="top" name="top"></router-view>
<router-view class="left" name="left"></router-view>
<router-view class="main" name="main"></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
.top {
float: left;
width: 100%;
height: 100px;
background-color: orange;
}
.left {
float: left;
width: 25%;
height: 700px;
background-color: #2c3e50;
}
.main {
float: left;
width: 75%;
height: 700px;
background-color: dimgray;
}
</style>