前言:現在 Vue 的路由已經開始大規模應用在單頁面應用上了。比較常見的就是路由網址中的 URL 里面的hash(#) ,這個 hash(#)來源于哪里那?沒錯就來自HTML 的錨點技術。大概如下原理:
一圖勝千言。
源代碼(省略好多h6標簽):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>#的作用</title>
</head>
<body>
<h1><a href="#title">跳轉到標記一 </a></h1>
<h6>一行車馬向東疾馳,行不數里,便有數騎馬迎來,馳到車前,翻身下馬,高聲向令狐沖致意,言語禮數,甚是恭敬。</h6>
<h1><a id="title">我是標記一</a></h1>
<h6>一行車馬向東疾馳,行不數里,便有數騎馬迎來,馳到車前,翻身下馬,高聲向令狐沖致意,言語禮數,甚是恭敬。</h6>
</body>
</html>
除了使用錨點技術之外,很明顯無論 React 還是 Vue 的路由大量使用了 H5 里面的,window.history
,最常用的就包含window.pushState({},"",url)
=>添加一條歷史記錄。window.history.replaceState()
=>替換當前URL, window.history.forward()
=> 瀏覽器的前進按鈕,window.history.back()
=> 瀏覽器的后退按鈕,window.history.go(number)
=>瀏覽器前進后退幾步,還有一個 hash 路由事件onhashchange
。
一、配置一個路由
Vue 官網:https://router.vuejs.org/zh/
項目安裝路由:
npm install --save vue-router
項目目錄的結構:這次目錄的結構做的真好看,制作方法是使用搜狗輸入法的(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 學習</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";
// 暴露路由需要的數組
export default [
{path : "/XiXi",component : XiXi},
{path : "/HaHa",component : HaHa}
];
App.vue
<template>
<div>
<h1>路由測試</h1>
<div class="box">
<p>我是一個盒子組件將會在這里顯示</p>
<!-- 使用 router-link 組件來導航. -->
<!-- 通過傳入 `to` 屬性指定鏈接. -->
<!-- <router-link> 默認會被渲染成一個 `<a>` 標簽 -->
<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 里面就是單純的兩個組件。
二、子路由
我們在子組件 HaHa 里面再放入孫子組件 Bar 。URL 變成http://127.0.0.1:8080/#/HaHa/bar
演示效果大約如下:
我們需要改動的地方有:
router.js 里面在組件 HaHa 下面加入 childrren 書寫子路由。與 react 不同的是子路由 不需要在前面加上 /
。
// 引入組件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
import Bar from "../components/Bar.vue";
// 暴露路由需要的數組
export default [
{path : "/XiXi",component : XiXi},
{
path : "/HaHa",
component : HaHa,
children:[
{path : "Bar",component:Bar}
]
}
];
除此之外還需要在 HaHa.vue 組件里面放上標簽<router-view></router-view>
,用來顯示孫子組件 Bar 。
嵌套路由如何指定默認路由:
{
path:"user",
name:user,
component: ()=>import("@/components/user.vue"),
children:[
{
path:"/",//path:"/"就是默認路由,同時path:""也是,自動訪問
name:index,
component: ()=>import("@/components/index.vue")
}
]
}
三、兩種路由跳轉方式
- 第一種 HTMl 方法
就是我們上面使用的<router-link to="/HaHa">Go to HaHa</router-link>
標簽,這個標簽的頁面效果就類似HTML里面的a
標簽。 - JS 方法
<a @click="$router.push('/HaHa/bar')">Go to HaHa</a>
我們在 a 標簽添加事件,因為配置過路由,$router
就像vuex的$store
一樣可以在任何子組件使用,我們只需要調用push
就能指定跳轉目標。
路由跳轉路徑可為分為兩種:字符串和對象
- 字符串上面我們使用的就是字符串
- 對象字面量方法,使用對象的時候我們可以:
-
{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'}}
-
四、使用路由進行頁面布局
有時候想同時 (同級) 展示多個視圖,而不是嵌套展示,就例如我們的有一個網頁頁面的頭部是一個 Header
組件,頁面底部是一個Footer
組件,頁面中間左邊是一個側邊欄 Slider
組件。右邊是頁面內容 Content
組件。而我們要求輸入的網址必須為:http://127.0.0.1:8080/#/index.html
原型圖如下:
首先我們要是用路由的重定向功能,讓網址一打開
http://127.0.0.1:8080/#/
就跳轉到http://127.0.0.1:8080/#/index.html
。路由文件添加代碼:
{ path: '/', redirect: "/index.html"}
插題路由重定向是可以重定向到自己內部的。
{
path:"/home",
redertec:"/index",
children:[
{
path:"/index",
name:"index",
children:[
]
}
]
}
下面來講講如何實現,默認已經完成基本布局HTML大致布局代碼:
<div class="box">
<!-- 網頁頭部 -->
<header>我是Header組件</header>
<!-- 網頁主體 -->
<main>
<aside>我是Slider側邊欄組件</aside>
<section>我是Content組件</section>
</main>
<!-- 網頁的底部 -->
<footer>我是Footer組件</footer>
</div>
我們只需要把每部分單獨提出來變成組件然后使用路由進行顯示。
- 命名視圖法
<header><router-view></router-view></header>
組件router-view
不加 name 屬性在路由文件里面組件將會默認填充到這里,如果寫了name屬性那么相應的組件會映射到相應的name
標簽。
我們現在把內容給提成四個組件:App.vue 配上路由就變成了
<!-- 網頁頭部 -->
<header><router-view></router-view></header>
<!-- 網頁主體 -->
<main>
<aside><router-view name="Slider"></router-view></aside>
<section><router-view name="Content"></router-view></section>
</main>
<!-- 網頁的底部 -->
<footer><router-view name="Footer"></router-view></footer>
然后我們只需要改變路由文件 router.js 文件,讓不同的路由視圖,顯示顯示對應的組件,只需要采用組件組的形式:
// 引入組件
import Header from "../components/Header.vue";
import Footer from "../components/Footer.vue";
import Slider from "../components/Slider.vue";
import Content from "../components/Content.vue";
// 暴露路由需要的數組
export default [
{ path: '/', redirect: "/index.html"},
{ path: '/index.html', components:{
default:Header,
Footer,
Slider,
Content
}}
];
五、面包屑導航
左邊的 Slider 組件作用使用 HTML 進行路由跳轉。代碼為:
<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)?
定義路由的時候可以配置 meta 字段:一個路由匹配到的所有路由記錄會暴露為$route
對象的$route.matched
數組。因此,我們需要遍歷$route.matched
來檢查路由記錄中的 meta 字段。
我們增加了兩個子組件,news 和 yule(娛樂)。
再回到 Content 組件里面。進行配置。
<div>
當前所在位置:
<router-link to="/">首頁</router-link>
{{$router.currentRoute.matched[1] ? "→ "+$router.currentRoute.matched[1].meta.chinese : null}}
<router-view></router-view>
</div>
猛一看可能比較懵,我們來看看 $router
身上攜帶了什么參數,我們直接在生命周期beforeCreate 里面打印出來$router
:
就一個 API 有用:
currentRoute
,在把currentRoute
打印出來console.log(this.$router.currentRoute);
各個參數用處大約如下:
// 路由模式
console.log(this.$router.mode);
//當前路由的路徑
console.log(this.$router.currentRoute.fullPath);
console.log(this.$router.currentRoute.path);
// URL路徑的查詢結果index.html?name=hero后面的東西{name: "hero"}
console.log(this.$router.currentRoute.query);
// 動態參數/:id,用戶輸入的id格式{id: "876543"}
console.log(this.$router.currentRoute.params);
// <router-view></router-view> 上的name屬性
console.log(this.$router.currentRoute.name);
// meta自定義傳入的參數,可以被我們使用
console.log(this.$router.currentRoute.meta.chinese);
// matched是一個數組,里面存放著滿足當前路徑的所有路由組件的路徑
console.log(this.$router.currentRoute.matched);
需要注意的是用到路由參數的時候,可能會失效,例如: /index.html/news 導航到 /index.html/yule,原來的組件實例會被復用。因為兩個路由都渲染同個組件,比起銷毀再創建,復用則顯得更加高效。重要的是生命周期鉤子不會再被調用。在視圖中如果使用路由參數(參數是對象或數組)插入,造成結果是視圖不更新,所以聲明式寫法必須沒有引用類型。除此之外解決辦法就只能使用官方推薦的方法:
使用 watch (監測變化) $router 對象:
watch: {
'$router' (to, from) {
// 對路由變化作出響應...
}
}
或者使用生命周期的 updated 來實現。親測可行,Vue 就是亂,官網上講不行,結果還是的以實際為準。
六、導航守衛(路由鉤子)
1. 組件內的守衛
- beforeRouteEnter 進入組件觸發
- beforeRouteUpdate (2.2 新增) 組件復用觸發
- beforeRouteLeave 離開組件觸發
接下來以 XiXi 組件為例,修改代碼加入路由進入和路由離開:
<template>
<div>
<h1>我是嘻嘻組件</h1>
</div>
</template>
<script>
export default {
// 組件內的守衛
// 導航離開該組件的對應路由時調用
beforeRouteLeave (to, from , next) {
// 這個離開守衛通常用來禁止用戶在還未保存修改前突然離開
const answer = window.confirm("你確定要離開當前頁面!");
if (answer) {
next();//確定跳轉
} else {
next(false);//導航可以通過 next(false) 來取消
}
},
beforeRouteEnter (to, from, next) {
// 進入該組件觸發
// 不!能!獲取組件實例 `this`
// 因為當守衛執行前,組件實例還沒被創建
const answer = window.confirm("你確定進入該頁面!");
if (answer) {
next(vm => {
// 通過 `vm` 訪問本組件的實例
console.log(vm)
});//確定進入
} else {
next(false);//不進入
}
}
}
</script>
<style lang="scss" scoped>
</style>
演示結果:
最最重要的就是
beforeRouteLeave
(將要離開當前路由),這個離開守衛通常用來禁止用戶在還未保存修改前突然離開,簡書寫文章時未保存,或者平時填寫一些表單退出時給出提示用到的就是這個。除了進入路由和離開路由還有個特殊的路由。為此特地新建一個組件 Foo,并配置路由為:{path : "/foo/:id",component : Foo}
。Foo組件的內容為:
<template>
<div>
<h1>被復用的組件</h1>
</div>
</template>
<script>
export default {
beforeRouteUpdate (to, from, next) {
// 在當前路由改變,但是該組件被復用時調用
// 舉例來說,對于一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。
// 可以訪問組件實例 `this`
const answer = window.confirm("你確定從/foo/1 => /foo/2 !");
if (answer) {
next(vm => {
// 通過 `vm` 訪問本組件的實例
console.log(vm)
});//確定進入
} else {
next(false);//不進入
}
}
}
</script>
<style lang="scss" scoped>
</style>
在 APP 組件在添加兩個跳轉按鈕:
<router-link to="/foo/1">/foo/1</router-link>
<router-link to="/foo/2">/foo/2</router-link>
查看演示效果:
每個守衛方法接收三個參數:
-
to: Route
: 即將要進入的目標 路由對象 -
from: Route:
當前導航正要離開的路由。 -
next: Function:
next(): 進行管道中的下一個鉤子。next(false): 中斷當前的導航。next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。
2. 路由獨享的守衛
可以在路由配置上直接定義 beforeEnter 守衛:此處以 HaHa 組件為例
beforeEnter: (to, from, next) => {
// 路由獨享的守衛
const answer = window.confirm("路由獨享的守衛!");
if (answer) {
next();//確定進入
} else {
next(false);//不進入
}
}
查看演示:
3. 全局前置守衛
當一個導航觸發時,全局前置守衛按照創建順序調用。
官網代碼案例:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
太簡單不演示了。
七、路由懶加載
當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件,這樣就更加高效了。
結合 Vue 的異步組件和 Webpack 的代碼分割功能,輕松實現路由組件的懶加載。
使用的形式其實就是組件的函數化:
以前:
component:About
現在:
component: () => import('./About.vue')
這是 Vue 的路由懶加載,還有 webpack 的懶加載方式,我同學項目中也有人用。基本沒變,看了秒懂:
component: r => require.ensure([], () => r(require('../page/home')), 'home')
詳情請去查看這個博客,里面最重要的話就是:
當然其實講了那么長的 require.ensure并沒有什么用,因為這個函數已經被 import() 取代了,但是考慮到之前的版本應該有很多人都是用 require.ensure 方法去加載的,所以還是講一下,而且其實 import 的執行過程跟 require.ensure 是一樣的,只不過用了更友好的語法而已,所以關于 import 的執行流程我也沒啥好講的了,感興趣的人看一下兩者的 API介紹就好了。
八、404 路由設置
常規參數只會匹配被/
分隔的 URL 片段中的字符。如果想匹配任意路徑,我們可以使用通配符 (*):
{
// 會匹配所有路徑
path: '*'
}
{
// 會匹配以 `/user-` 開頭的任意路徑
path: '/user-*'
}
當使用通配符路由時,請確保路由的順序是正確的,也就是說含有通配符的路由應該放在最后。路由 { path: '*' } 通常用于客戶端 404 錯誤。當使用一個通配符時,$router.currentRoute.params
內會自動添加一個名為 pathMatch
參數。它包含了 URL 通過通配符被匹配的部分:
例如輸入錯誤的網址為:http://127.0.0.1:8080/#/index
$router.currentRoute.params.pathMatch
匹配輸出為:/index
八、滾動行為
滾動行為:使用前端路由,當切換到新路由時,想要頁面滾到自己指定位置,或者是保持原先的滾動位置,就像重新加載頁面那樣。
const router = new VueRouter({
routes,
scrollBehavior (to, from, savedPosition) {
// return 期望滾動到哪個的位置切換滾動到
return { x: 0, y: 1000 }
}
});