前言:現(xiàn)在 Vue 的路由已經(jīng)開始大規(guī)模應(yīng)用在單頁面應(yīng)用上了。比較常見的就是路由網(wǎng)址中的 URL 里面的hash(#) 哼绑,這個(gè) hash(#)來源于哪里那杰赛?沒錯(cuò)就來自HTML 的錨點(diǎn)技術(shù)稼稿。大概如下原理:
一圖勝千言册招。
源代碼(省略好多h6標(biāo)簽):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>#的作用</title>
</head>
<body>
<h1><a href="#title">跳轉(zhuǎn)到標(biāo)記一 </a></h1>
<h6>一行車馬向東疾馳,行不數(shù)里萎攒,便有數(shù)騎馬迎來遇八,馳到車前,翻身下馬耍休,高聲向令狐沖致意刃永,言語禮數(shù),甚是恭敬羊精。</h6>
<h1><a id="title">我是標(biāo)記一</a></h1>
<h6>一行車馬向東疾馳斯够,行不數(shù)里,便有數(shù)騎馬迎來喧锦,馳到車前读规,翻身下馬,高聲向令狐沖致意裸违,言語禮數(shù)掖桦,甚是恭敬。</h6>
</body>
</html>
除了使用錨點(diǎn)技術(shù)之外供汛,很明顯無論 React 還是 Vue 的路由大量使用了 H5 里面的,window.history
涌穆,最常用的就包含window.pushState({},"",url)
=>添加一條歷史記錄怔昨。window.history.replaceState()
=>替換當(dāng)前URL, window.history.forward()
=> 瀏覽器的前進(jìn)按鈕宿稀,window.history.back()
=> 瀏覽器的后退按鈕趁舀,window.history.go(number)
=>瀏覽器前進(jìn)后退幾步,還有一個(gè) hash 路由事件onhashchange
祝沸。
一矮烹、配置一個(gè)路由
Vue 官網(wǎng):https://router.vuejs.org/zh/
項(xiàng)目安裝路由:
npm install --save vue-router
項(xiàng)目目錄的結(jié)構(gòu):這次目錄的結(jié)構(gòu)做的真好看越庇,制作方法是使用搜狗輸入法的(ctr+shift+z)制表符。
┏ components
┃ ┣ HaHa.vue
┃ ┗ XiXi.vue
┣ router
┃ ┗ router.js
┣ App.vue
┣ main.js
┗ index.html
最終的路由效果:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 學(xué)習(xí)</title>
</head>
<body>
<div id="app"></div>
<script src = "./virtual/bundle.js"></script>
</body>
</html>
main.js 里 vue-router 配置四步走
import Vue from "vue";
import App from "./App.vue";
// ①
import VueRouter from "vue-router";
import routes from "./router/router.js";
// 使用路由②
Vue.use(VueRouter);
// ③
const router = new VueRouter({
routes
})
new Vue({
el:"#app",
render(h){
return h(App);
},
// ④
router
});
router.js
// 引入組件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
// 暴露路由需要的數(shù)組
export default [
{path : "/XiXi",component : XiXi},
{path : "/HaHa",component : HaHa}
];
App.vue
<template>
<div>
<h1>路由測(cè)試</h1>
<div class="box">
<p>我是一個(gè)盒子組件將會(huì)在這里顯示</p>
<!-- 使用 router-link 組件來導(dǎo)航. -->
<!-- 通過傳入 `to` 屬性指定鏈接. -->
<!-- <router-link> 默認(rèn)會(huì)被渲染成一個(gè) `<a>` 標(biāo)簽 -->
<router-link to="/HaHa">Go to HaHa</router-link>
<router-link to="/XiXi">Go to XiXi</router-link>
<router-link to="/">Go to home</router-link>
<!-- 路由出口 -->
<!-- 路由匹配到的組件將渲染在這里 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.box{
width: 500px;
height: 200px;
border:20px solid #5e5ed9;
}
</style>
components 里面就是單純的兩個(gè)組件奉狈。
二卤唉、子路由
我們?cè)谧咏M件 HaHa 里面再放入孫子組件 Bar 。URL 變成http://127.0.0.1:8080/#/HaHa/bar
演示效果大約如下:
我們需要改動(dòng)的地方有:
router.js 里面在組件 HaHa 下面加入 childrren 書寫子路由仁期。與 react 不同的是子路由 不需要在前面加上 /
桑驱。
// 引入組件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
import Bar from "../components/Bar.vue";
// 暴露路由需要的數(shù)組
export default [
{path : "/XiXi",component : XiXi},
{
path : "/HaHa",
component : HaHa,
children:[
{path : "Bar",component:Bar}
]
}
];
除此之外還需要在 HaHa.vue 組件里面放上標(biāo)簽<router-view></router-view>
,用來顯示孫子組件 Bar 跛蛋。
嵌套路由如何指定默認(rèn)路由:
{
path:"user",
name:user,
component: ()=>import("@/components/user.vue"),
children:[
{
path:"/",//path:"/"就是默認(rèn)路由熬的,同時(shí)path:""也是,自動(dòng)訪問
name:index,
component: ()=>import("@/components/index.vue")
}
]
}
三赊级、兩種路由跳轉(zhuǎn)方式
- 第一種 HTMl 方法
就是我們上面使用的<router-link to="/HaHa">Go to HaHa</router-link>
標(biāo)簽押框,這個(gè)標(biāo)簽的頁面效果就類似HTML里面的a
標(biāo)簽。 - JS 方法
<a @click="$router.push('/HaHa/bar')">Go to HaHa</a>
我們?cè)?a 標(biāo)簽添加事件理逊,因?yàn)榕渲眠^路由强戴,$router
就像vuex的$store
一樣可以在任何子組件使用,我們只需要調(diào)用push
就能指定跳轉(zhuǎn)目標(biāo)挡鞍。
路由跳轉(zhuǎn)路徑可為分為兩種:字符串和對(duì)象
- 字符串上面我們使用的就是字符串
- 對(duì)象字面量方法骑歹,使用對(duì)象的時(shí)候我們可以:
-
{path:'/HaHa'}
,還可以傳參分為 params和query的方式墨微,{path:'/HaHa',params:{userId:123}}
,{path:'/HaHa',query:{name:'hero'}}
-
{name:'haha'}
命名路由,也可以傳參也分為 params和query的方式道媚,{path:'/HaHa',params:{userId:123}}
,{path:'/HaHa',query:{name:'hero'}}
-
四、使用路由進(jìn)行頁面布局
有時(shí)候想同時(shí) (同級(jí)) 展示多個(gè)視圖翘县,而不是嵌套展示最域,就例如我們的有一個(gè)網(wǎng)頁頁面的頭部是一個(gè) Header
組件,頁面底部是一個(gè)Footer
組件锈麸,頁面中間左邊是一個(gè)側(cè)邊欄 Slider
組件镀脂。右邊是頁面內(nèi)容 Content
組件。而我們要求輸入的網(wǎng)址必須為:http://127.0.0.1:8080/#/index.html
原型圖如下:
首先我們要是用路由的重定向功能忘伞,讓網(wǎng)址一打開
http://127.0.0.1:8080/#/
就跳轉(zhuǎn)到http://127.0.0.1:8080/#/index.html
薄翅。路由文件添加代碼:
{ path: '/', redirect: "/index.html"}
插題路由重定向是可以重定向到自己內(nèi)部的。
{
path:"/home",
redertec:"/index",
children:[
{
path:"/index",
name:"index",
children:[
]
}
]
}
下面來講講如何實(shí)現(xiàn)氓奈,默認(rèn)已經(jīng)完成基本布局HTML大致布局代碼:
<div class="box">
<!-- 網(wǎng)頁頭部 -->
<header>我是Header組件</header>
<!-- 網(wǎng)頁主體 -->
<main>
<aside>我是Slider側(cè)邊欄組件</aside>
<section>我是Content組件</section>
</main>
<!-- 網(wǎng)頁的底部 -->
<footer>我是Footer組件</footer>
</div>
我們只需要把每部分單獨(dú)提出來變成組件然后使用路由進(jìn)行顯示翘魄。
- 命名視圖法
<header><router-view></router-view></header>
組件router-view
不加 name 屬性在路由文件里面組件將會(huì)默認(rèn)填充到這里,如果寫了name屬性那么相應(yīng)的組件會(huì)映射到相應(yīng)的name
標(biāo)簽舀奶。
我們現(xiàn)在把內(nèi)容給提成四個(gè)組件:App.vue 配上路由就變成了
<!-- 網(wǎng)頁頭部 -->
<header><router-view></router-view></header>
<!-- 網(wǎng)頁主體 -->
<main>
<aside><router-view name="Slider"></router-view></aside>
<section><router-view name="Content"></router-view></section>
</main>
<!-- 網(wǎng)頁的底部 -->
<footer><router-view name="Footer"></router-view></footer>
然后我們只需要改變路由文件 router.js 文件暑竟,讓不同的路由視圖,顯示顯示對(duì)應(yīng)的組件,只需要采用組件組的形式:
// 引入組件
import Header from "../components/Header.vue";
import Footer from "../components/Footer.vue";
import Slider from "../components/Slider.vue";
import Content from "../components/Content.vue";
// 暴露路由需要的數(shù)組
export default [
{ path: '/', redirect: "/index.html"},
{ path: '/index.html', components:{
default:Header,
Footer,
Slider,
Content
}}
];
五糊闽、面包屑導(dǎo)航
左邊的 Slider 組件作用使用 HTML 進(jìn)行路由跳轉(zhuǎn)。代碼為:
<template>
<div>
<aside>
<div>
<router-link class="cur" to="/">首頁</router-link>
</div>
<div>
<router-link class="cur" to="/index.html/yule">娛樂</router-link>
</div>
<div>
<router-link class="cur" to="/index.html/news">新聞</router-link>
</div>
</aside>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.cur{
display:inline-block;
margin:20px 30px;
text-decoration: none;
color:#666;
font-size: 16px;
}
</style>
然后去更改路由文件router.js:
{
path: '/index.html',
components : {
default:Header,
Footer,
Slider,
Content
},
meta:{chinese:"首頁"},
children : [
{ path : "news" ,component : News,meta:{chinese:"新聞"}},
{ path : "yule" ,component : Yule,meta:{chinese:"娛樂"}}
]
}
路由元信息(meta)?
定義路由的時(shí)候可以配置 meta 字段:一個(gè)路由匹配到的所有路由記錄會(huì)暴露為$route
對(duì)象的$route.matched
數(shù)組诅蝶。因此腹躁,我們需要遍歷$route.matched
來檢查路由記錄中的 meta 字段桑包。
我們?cè)黾恿藘蓚€(gè)子組件,news 和 yule(娛樂)潜慎。
再回到 Content 組件里面捡多。進(jìn)行配置。
<div>
當(dāng)前所在位置:
<router-link to="/">首頁</router-link>
{{$router.currentRoute.matched[1] ? "→ "+$router.currentRoute.matched[1].meta.chinese : null}}
<router-view></router-view>
</div>
猛一看可能比較懵铐炫,我們來看看 $router
身上攜帶了什么參數(shù)垒手,我們直接在生命周期beforeCreate 里面打印出來$router
:
就一個(gè) API 有用:
currentRoute
,在把currentRoute
打印出來console.log(this.$router.currentRoute);
各個(gè)參數(shù)用處大約如下:
// 路由模式
console.log(this.$router.mode);
//當(dāng)前路由的路徑
console.log(this.$router.currentRoute.fullPath);
console.log(this.$router.currentRoute.path);
// URL路徑的查詢結(jié)果index.html?name=hero后面的東西{name: "hero"}
console.log(this.$router.currentRoute.query);
// 動(dòng)態(tài)參數(shù)/:id倒信,用戶輸入的id格式{id: "876543"}
console.log(this.$router.currentRoute.params);
// <router-view></router-view> 上的name屬性
console.log(this.$router.currentRoute.name);
// meta自定義傳入的參數(shù)科贬,可以被我們使用
console.log(this.$router.currentRoute.meta.chinese);
// matched是一個(gè)數(shù)組,里面存放著滿足當(dāng)前路徑的所有路由組件的路徑
console.log(this.$router.currentRoute.matched);
需要注意的是用到路由參數(shù)的時(shí)候,可能會(huì)失效鳖悠,例如: /index.html/news 導(dǎo)航到 /index.html/yule榜掌,原來的組件實(shí)例會(huì)被復(fù)用。因?yàn)閮蓚€(gè)路由都渲染同個(gè)組件乘综,比起銷毀再創(chuàng)建憎账,復(fù)用則顯得更加高效。重要的是生命周期鉤子不會(huì)再被調(diào)用卡辰。在視圖中如果使用路由參數(shù)(參數(shù)是對(duì)象或數(shù)組)插入胞皱,造成結(jié)果是視圖不更新,所以聲明式寫法必須沒有引用類型九妈。除此之外解決辦法就只能使用官方推薦的方法:
使用 watch (監(jiān)測(cè)變化) $router 對(duì)象:
watch: {
'$router' (to, from) {
// 對(duì)路由變化作出響應(yīng)...
}
}
或者使用生命周期的 updated 來實(shí)現(xiàn)反砌。親測(cè)可行,Vue 就是亂萌朱,官網(wǎng)上講不行宴树,結(jié)果還是的以實(shí)際為準(zhǔn)。
六晶疼、導(dǎo)航守衛(wèi)(路由鉤子)
1. 組件內(nèi)的守衛(wèi)
- beforeRouteEnter 進(jìn)入組件觸發(fā)
- beforeRouteUpdate (2.2 新增) 組件復(fù)用觸發(fā)
- beforeRouteLeave 離開組件觸發(fā)
接下來以 XiXi 組件為例酒贬,修改代碼加入路由進(jìn)入和路由離開:
<template>
<div>
<h1>我是嘻嘻組件</h1>
</div>
</template>
<script>
export default {
// 組件內(nèi)的守衛(wèi)
// 導(dǎo)航離開該組件的對(duì)應(yīng)路由時(shí)調(diào)用
beforeRouteLeave (to, from , next) {
// 這個(gè)離開守衛(wèi)通常用來禁止用戶在還未保存修改前突然離開
const answer = window.confirm("你確定要離開當(dāng)前頁面!");
if (answer) {
next();//確定跳轉(zhuǎn)
} else {
next(false);//導(dǎo)航可以通過 next(false) 來取消
}
},
beforeRouteEnter (to, from, next) {
// 進(jìn)入該組件觸發(fā)
// 不冒晰!能同衣!獲取組件實(shí)例 `this`
// 因?yàn)楫?dāng)守衛(wèi)執(zhí)行前,組件實(shí)例還沒被創(chuàng)建
const answer = window.confirm("你確定進(jìn)入該頁面壶运!");
if (answer) {
next(vm => {
// 通過 `vm` 訪問本組件的實(shí)例
console.log(vm)
});//確定進(jìn)入
} else {
next(false);//不進(jìn)入
}
}
}
</script>
<style lang="scss" scoped>
</style>
演示結(jié)果:
最最重要的就是
beforeRouteLeave
(將要離開當(dāng)前路由),這個(gè)離開守衛(wèi)通常用來禁止用戶在還未保存修改前突然離開浪秘,簡(jiǎn)書寫文章時(shí)未保存蒋情,或者平時(shí)填寫一些表單退出時(shí)給出提示用到的就是這個(gè)埠况。除了進(jìn)入路由和離開路由還有個(gè)特殊的路由。為此特地新建一個(gè)組件 Foo棵癣,并配置路由為:{path : "/foo/:id",component : Foo}
辕翰。Foo組件的內(nèi)容為:
<template>
<div>
<h1>被復(fù)用的組件</h1>
</div>
</template>
<script>
export default {
beforeRouteUpdate (to, from, next) {
// 在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用
// 舉例來說狈谊,對(duì)于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 /foo/:id喜命,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時(shí)候,
// 由于會(huì)渲染同樣的 Foo 組件河劝,因此組件實(shí)例會(huì)被復(fù)用壁榕。而這個(gè)鉤子就會(huì)在這個(gè)情況下被調(diào)用。
// 可以訪問組件實(shí)例 `this`
const answer = window.confirm("你確定從/foo/1 => /foo/2 赎瞎!");
if (answer) {
next(vm => {
// 通過 `vm` 訪問本組件的實(shí)例
console.log(vm)
});//確定進(jìn)入
} else {
next(false);//不進(jìn)入
}
}
}
</script>
<style lang="scss" scoped>
</style>
在 APP 組件在添加兩個(gè)跳轉(zhuǎn)按鈕:
<router-link to="/foo/1">/foo/1</router-link>
<router-link to="/foo/2">/foo/2</router-link>
查看演示效果:
每個(gè)守衛(wèi)方法接收三個(gè)參數(shù):
-
to: Route
: 即將要進(jìn)入的目標(biāo) 路由對(duì)象 -
from: Route:
當(dāng)前導(dǎo)航正要離開的路由牌里。 -
next: Function:
next(): 進(jìn)行管道中的下一個(gè)鉤子。next(false): 中斷當(dāng)前的導(dǎo)航务甥。next('/') 或者 next({ path: '/' }): 跳轉(zhuǎn)到一個(gè)不同的地址牡辽。
2. 路由獨(dú)享的守衛(wèi)
可以在路由配置上直接定義 beforeEnter 守衛(wèi):此處以 HaHa 組件為例
beforeEnter: (to, from, next) => {
// 路由獨(dú)享的守衛(wèi)
const answer = window.confirm("路由獨(dú)享的守衛(wèi)!");
if (answer) {
next();//確定進(jìn)入
} else {
next(false);//不進(jìn)入
}
}
查看演示:
3. 全局前置守衛(wèi)
當(dāng)一個(gè)導(dǎo)航觸發(fā)時(shí)敞临,全局前置守衛(wèi)按照創(chuàng)建順序調(diào)用态辛。
官網(wǎng)代碼案例:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
太簡(jiǎn)單不演示了。
七挺尿、路由懶加載
當(dāng)打包構(gòu)建應(yīng)用時(shí),JavaScript 包會(huì)變得非常大票髓,影響頁面加載攀涵。如果我們能把不同路由對(duì)應(yīng)的組件分割成不同的代碼塊,然后當(dāng)路由被訪問的時(shí)候才加載對(duì)應(yīng)組件洽沟,這樣就更加高效了以故。
結(jié)合 Vue 的異步組件和 Webpack 的代碼分割功能,輕松實(shí)現(xiàn)路由組件的懶加載裆操。
使用的形式其實(shí)就是組件的函數(shù)化:
以前:
component:About
現(xiàn)在:
component: () => import('./About.vue')
這是 Vue 的路由懶加載怒详,還有 webpack 的懶加載方式,我同學(xué)項(xiàng)目中也有人用踪区±ニ福基本沒變,看了秒懂:
component: r => require.ensure([], () => r(require('../page/home')), 'home')
詳情請(qǐng)去查看這個(gè)博客缎岗,里面最重要的話就是:
當(dāng)然其實(shí)講了那么長(zhǎng)的 require.ensure并沒有什么用,因?yàn)檫@個(gè)函數(shù)已經(jīng)被 import() 取代了,但是考慮到之前的版本應(yīng)該有很多人都是用 require.ensure 方法去加載的,所以還是講一下,而且其實(shí) import 的執(zhí)行過程跟 require.ensure 是一樣的,只不過用了更友好的語法而已,所以關(guān)于 import 的執(zhí)行流程我也沒啥好講的了,感興趣的人看一下兩者的 API介紹就好了静尼。
八、404 路由設(shè)置
常規(guī)參數(shù)只會(huì)匹配被/
分隔的 URL 片段中的字符。如果想匹配任意路徑鼠渺,我們可以使用通配符 (*):
{
// 會(huì)匹配所有路徑
path: '*'
}
{
// 會(huì)匹配以 `/user-` 開頭的任意路徑
path: '/user-*'
}
當(dāng)使用通配符路由時(shí)鸭巴,請(qǐng)確保路由的順序是正確的,也就是說含有通配符的路由應(yīng)該放在最后拦盹。路由 { path: '*' } 通常用于客戶端 404 錯(cuò)誤鹃祖。當(dāng)使用一個(gè)通配符時(shí),$router.currentRoute.params
內(nèi)會(huì)自動(dòng)添加一個(gè)名為 pathMatch
參數(shù)普舆。它包含了 URL 通過通配符被匹配的部分:
例如輸入錯(cuò)誤的網(wǎng)址為:http://127.0.0.1:8080/#/index
$router.currentRoute.params.pathMatch
匹配輸出為:/index
八恬口、滾動(dòng)行為
滾動(dòng)行為:使用前端路由,當(dāng)切換到新路由時(shí)沼侣,想要頁面滾到自己指定位置祖能,或者是保持原先的滾動(dòng)位置,就像重新加載頁面那樣华临。
const router = new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
// return 期望滾動(dòng)到哪個(gè)的位置切換滾動(dòng)到
return { x: 0, y: 1000 }
}
});