一、URL中的#號
1、簡介
在單頁面應用程序 (SPA - single page application) 中,URL中一般都會包含#。
井號#(Hash): 位置標識符, 代表網頁中的一個位置,用於前端的URL中(注意:井號#只用於前端URL中不能在HTTP請求中使用,#是用來讓瀏覽器滾動的,對服務器端完全無用),其右面的字符就是該位置的標識符。比如http://www.example.com/index.html#location1
就代表index.html中的location1位置。瀏覽器讀取這個URL後,會自動將location1位置滾動至可視區域。在第一個#後面出現的任何字符,都會被瀏覽器解讀爲位置標識符。
爲網頁位置指定標識符,有兩個方法:
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>